-
1
require 'active_support/lazy_load_hooks'
-
1
require 'active_record/deprecated_finders/version'
-
-
1
ActiveSupport.on_load(:active_record) do
-
1
require 'active_record/deprecated_finders/base'
-
1
require 'active_record/deprecated_finders/relation'
-
1
require 'active_record/deprecated_finders/dynamic_matchers'
-
1
require 'active_record/deprecated_finders/collection_proxy'
-
1
require 'active_record/deprecated_finders/association_builder'
-
end
-
1
require 'active_record/associations/builder/association'
-
1
require 'active_support/core_ext/module/aliasing'
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord::Associations::Builder
-
1
class DeprecatedOptionsProc
-
1
attr_reader :options
-
-
1
def initialize(options)
-
options[:includes] = options.delete(:include) if options[:include]
-
options[:where] = options.delete(:conditions) if options[:conditions]
-
options[:extending] = options.delete(:extend) if options[:extend]
-
-
@options = options
-
end
-
-
1
def to_proc
-
options = self.options
-
proc do |owner|
-
if options[:where].respond_to?(:to_proc)
-
context = owner || self
-
where(context.instance_eval(&options[:where]))
-
.merge!(options.except(:where))
-
else
-
merge(options)
-
end
-
end
-
end
-
-
1
def arity
-
1
-
end
-
end
-
-
1
class Association
-
1
DEPRECATED_OPTIONS = [:readonly, :order, :limit, :group, :having,
-
:offset, :select, :uniq, :include, :conditions, :extend]
-
-
1
self.valid_options += [:select, :conditions, :include, :extend, :readonly]
-
-
1
def initialize_with_deprecated_options(model, name, scope, options)
-
9
if scope.is_a?(Hash)
-
2
options = scope
-
2
deprecated_options = options.slice(*DEPRECATED_OPTIONS)
-
-
2
if deprecated_options.empty?
-
2
scope = nil
-
else
-
ActiveSupport::Deprecation.warn(
-
"The following options in your #{model.name}.#{macro} :#{name} declaration are deprecated: " \
-
"#{deprecated_options.keys.map(&:inspect).join(',')}. Please use a scope block instead. " \
-
"For example, the following:\n" \
-
"\n" \
-
" has_many :spam_comments, conditions: { spam: true }, class_name: 'Comment'\n" \
-
"\n" \
-
"should be rewritten as the following:\n" \
-
"\n" \
-
" has_many :spam_comments, -> { where spam: true }, class_name: 'Comment'\n"
-
)
-
scope = DeprecatedOptionsProc.new(deprecated_options)
-
options = options.except(*DEPRECATED_OPTIONS)
-
end
-
end
-
-
9
initialize_without_deprecated_options(model, name, scope, options)
-
end
-
-
1
alias_method_chain :initialize, :deprecated_options
-
end
-
-
1
class CollectionAssociation
-
include Module.new {
-
1
def valid_options
-
5
super + [:order, :group, :having, :limit, :offset, :uniq]
-
end
-
1
}
-
end
-
end
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord
-
1
module DeprecatedFinders
-
1
class ScopeWrapper
-
1
def self.wrap(klass, scope)
-
1
if scope.is_a?(Hash)
-
ActiveSupport::Deprecation.warn(
-
"Calling #scope or #default_scope with a hash is deprecated. Please use a lambda " \
-
"containing a scope. E.g. scope :red, -> { where(color: 'red') }"
-
)
-
-
new(klass, scope)
-
1
elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
-
1
new(klass, scope)
-
else
-
scope
-
end
-
end
-
-
1
def initialize(klass, scope)
-
1
@klass = klass
-
1
@scope = scope
-
end
-
-
1
def call(*args)
-
if @scope.respond_to?(:call)
-
result = @scope.call(*args)
-
-
if result.is_a?(Hash)
-
msg = "Returning a hash from a #scope or #default_scope block is deprecated. Please " \
-
"return an actual scope object instead. E.g. scope :red, -> { where(color: 'red') } " \
-
"rather than scope :red, -> { { conditions: { color: 'red' } } }. "
-
-
if @scope.respond_to?(:source_location)
-
msg << "(The scope was defined at #{@scope.source_location.join(':')}.)"
-
end
-
-
ActiveSupport::Deprecation.warn(msg)
-
end
-
else
-
result = @scope
-
end
-
-
if result.is_a?(Hash)
-
@klass.unscoped.apply_finder_options(result, true)
-
else
-
result
-
end
-
end
-
end
-
-
1
def default_scope(scope = {}, &block)
-
if block_given?
-
super ScopeWrapper.new(self, block), &nil
-
else
-
super ScopeWrapper.wrap(self, scope)
-
end
-
end
-
-
1
def scoped(options = nil)
-
ActiveSupport::Deprecation.warn("Model.scoped is deprecated. Please use Model.all instead.")
-
options ? all.apply_finder_options(options, true) : all
-
end
-
-
1
def all(options = nil)
-
options ? super().all(options) : super()
-
end
-
-
1
def scope(name, body = {}, &block)
-
1
super(name, ScopeWrapper.wrap(self, body), &block)
-
end
-
-
1
def with_scope(scope = {}, action = :merge)
-
ActiveSupport::Deprecation.warn(
-
"ActiveRecord::Base#with_scope and #with_exclusive_scope are deprecated. " \
-
"Please use ActiveRecord::Relation#scoping instead. (You can use #merge " \
-
"to merge multiple scopes together.)"
-
)
-
-
# If another Active Record class has been passed in, get its current scope
-
scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
-
-
previous_scope = self.current_scope
-
-
if scope.is_a?(Hash)
-
# Dup first and second level of hash (method and params).
-
scope = scope.dup
-
scope.each do |method, params|
-
scope[method] = params.dup unless params == true
-
end
-
-
scope.assert_valid_keys([ :find, :create ])
-
relation = construct_finder_arel(scope[:find] || {})
-
relation.default_scoped = true unless action == :overwrite
-
-
if previous_scope && previous_scope.create_with_value && scope[:create]
-
scope_for_create = if action == :merge
-
previous_scope.create_with_value.merge(scope[:create])
-
else
-
scope[:create]
-
end
-
-
relation = relation.create_with(scope_for_create)
-
else
-
scope_for_create = scope[:create]
-
scope_for_create ||= previous_scope.create_with_value if previous_scope
-
relation = relation.create_with(scope_for_create) if scope_for_create
-
end
-
-
scope = relation
-
end
-
-
scope = previous_scope.merge(scope) if previous_scope && action == :merge
-
scope.scoping { yield }
-
end
-
-
1
protected
-
-
# Works like with_scope, but discards any nested properties.
-
1
def with_exclusive_scope(method_scoping = {}, &block)
-
if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
-
raise ArgumentError, <<-MSG
-
New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
-
-
User.unscoped.where(:active => true)
-
-
Or call unscoped with a block:
-
-
User.unscoped do
-
User.where(:active => true).all
-
end
-
-
MSG
-
end
-
with_scope(method_scoping, :overwrite, &block)
-
end
-
-
1
private
-
-
1
def construct_finder_arel(options = {}, scope = nil)
-
relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options, true) : options
-
relation = scope.merge(relation) if scope
-
relation
-
end
-
end
-
-
1
class Base
-
1
extend DeprecatedFinders
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class CollectionProxy
-
1
def method_missing(method, *args, &block)
-
match = DynamicMatchers::Method.match(klass, method)
-
-
if match && match.is_a?(DynamicMatchers::Instantiator)
-
super do |record|
-
proxy_association.add_to_target(record)
-
yield record if block_given?
-
end
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord
-
1
module DynamicMatchers
-
1
module DeprecatedFinder
-
1
def body
-
<<-CODE
-
result = #{super}
-
result && block_given? ? yield(result) : result
-
CODE
-
end
-
-
1
def result
-
"all.apply_finder_options(options, true).#{super}"
-
end
-
-
1
def signature
-
"#{super}, options = {}"
-
end
-
end
-
-
1
module DeprecationWarning
-
1
def body
-
"#{deprecation_warning}\n#{super}"
-
end
-
-
1
def deprecation_warning
-
%{ActiveSupport::Deprecation.warn("This dynamic method is deprecated. Please use e.g. #{deprecation_alternative} instead.")}
-
end
-
end
-
-
1
module FindByDeprecationWarning
-
1
def body
-
<<-CODE
-
if block_given?
-
ActiveSupport::Deprecation.warn("Calling find_by or find_by! methods with a block is deprecated with no replacement.")
-
end
-
-
unless options.empty?
-
ActiveSupport::Deprecation.warn(
-
"Calling find_by or find_by! methods with options is deprecated. " \
-
"Build a scope instead, e.g. User.where('age > 21').find_by_name('Jon')."
-
)
-
end
-
-
#{super}
-
CODE
-
end
-
end
-
-
1
class FindBy
-
1
include DeprecatedFinder
-
1
include FindByDeprecationWarning
-
end
-
-
1
class FindByBang
-
1
include DeprecatedFinder
-
1
include FindByDeprecationWarning
-
end
-
-
1
class FindAllBy < Method
-
1
Method.matchers << self
-
1
include Finder
-
1
include DeprecatedFinder
-
1
include DeprecationWarning
-
-
1
def self.prefix
-
1
"find_all_by"
-
end
-
-
1
def finder
-
"where"
-
end
-
-
1
def result
-
"#{super}.to_a"
-
end
-
-
1
def deprecation_alternative
-
"Post.where(...).all"
-
end
-
end
-
-
1
class FindLastBy < Method
-
1
Method.matchers << self
-
1
include Finder
-
1
include DeprecatedFinder
-
1
include DeprecationWarning
-
-
1
def self.prefix
-
1
"find_last_by"
-
end
-
-
1
def finder
-
"where"
-
end
-
-
1
def result
-
"#{super}.last"
-
end
-
-
1
def deprecation_alternative
-
"Post.where(...).last"
-
end
-
end
-
-
1
class ScopedBy < Method
-
1
Method.matchers << self
-
1
include Finder
-
1
include DeprecationWarning
-
-
1
def self.prefix
-
1
"scoped_by"
-
end
-
-
1
def body
-
"#{deprecation_warning} \n where(#{attributes_hash})"
-
end
-
-
1
def deprecation_alternative
-
"Post.where(...)"
-
end
-
end
-
-
1
class Instantiator < Method
-
1
include DeprecationWarning
-
-
1
def self.dispatch(klass, attribute_names, instantiator, args, block)
-
if args.length == 1 && args.first.is_a?(Hash)
-
attributes = args.first.stringify_keys
-
conditions = attributes.slice(*attribute_names)
-
rest = [attributes.except(*attribute_names)]
-
else
-
raise ArgumentError, "too few arguments" unless args.length >= attribute_names.length
-
-
conditions = Hash[attribute_names.map.with_index { |n, i| [n, args[i]] }]
-
rest = args.drop(attribute_names.length)
-
end
-
-
klass.where(conditions).first ||
-
klass.create_with(conditions).send(instantiator, *rest, &block)
-
end
-
-
1
def signature
-
"*args, &block"
-
end
-
-
1
def body
-
<<-CODE
-
#{deprecation_warning}
-
#{self.class}.dispatch(self, #{attribute_names.inspect}, #{instantiator.inspect}, args, block)
-
CODE
-
end
-
-
1
def instantiator
-
raise NotImplementedError
-
end
-
-
1
def deprecation_alternative
-
"Post.#{self.class.prefix}#{self.class.suffix}(name: 'foo')"
-
end
-
end
-
-
1
class FindOrInitializeBy < Instantiator
-
1
Method.matchers << self
-
-
1
def self.prefix
-
1
"find_or_initialize_by"
-
end
-
-
1
def instantiator
-
"new"
-
end
-
end
-
-
1
class FindOrCreateBy < Instantiator
-
1
Method.matchers << self
-
-
1
def self.prefix
-
1
"find_or_create_by"
-
end
-
-
1
def instantiator
-
"create"
-
end
-
end
-
-
1
class FindOrCreateByBang < Instantiator
-
1
Method.matchers << self
-
-
1
def self.prefix
-
1
"find_or_create_by"
-
end
-
-
1
def self.suffix
-
1
"!"
-
end
-
-
1
def instantiator
-
"create!"
-
end
-
end
-
end
-
end
-
1
require 'active_record/relation'
-
1
require 'active_support/core_ext/module/aliasing'
-
-
1
module ActiveRecord
-
1
class Relation
-
1
module DeprecatedMethods
-
1
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend,
-
:order, :select, :readonly, :group, :having, :from, :lock ]
-
-
# The silence_deprecation arg is for internal use, where we have already output a
-
# deprecation further up the call stack.
-
1
def apply_finder_options(options, silence_deprecation = false)
-
ActiveSupport::Deprecation.warn("#apply_finder_options is deprecated") unless silence_deprecation
-
-
relation = clone
-
return relation unless options
-
-
options.assert_valid_keys(VALID_FIND_OPTIONS)
-
finders = options.dup
-
finders.delete_if { |key, value| value.nil? && key != :limit }
-
-
((VALID_FIND_OPTIONS - [:conditions, :include, :extend]) & finders.keys).each do |finder|
-
relation = relation.send(finder, finders[finder])
-
end
-
-
relation = relation.where(finders[:conditions]) if options.has_key?(:conditions)
-
relation = relation.includes(finders[:include]) if options.has_key?(:include)
-
relation = relation.extending(finders[:extend]) if options.has_key?(:extend)
-
-
relation
-
end
-
-
1
def update_all_with_deprecated_options(updates, conditions = nil, options = {})
-
scope = self
-
-
if conditions
-
scope = where(conditions)
-
-
ActiveSupport::Deprecation.warn(
-
"Relation#update_all with conditions is deprecated. Please use " \
-
"Item.where(color: 'red').update_all(...) rather than " \
-
"Item.update_all(..., color: 'red')."
-
)
-
end
-
-
if options.present?
-
scope = scope.apply_finder_options(options.slice(:limit, :order), true)
-
-
ActiveSupport::Deprecation.warn(
-
"Relation#update_all with :limit / :order options is deprecated. " \
-
"Please use e.g. Post.limit(1).order(:foo).update_all instead."
-
)
-
end
-
-
scope.update_all_without_deprecated_options(updates)
-
end
-
-
1
def find_in_batches(options = {}, &block)
-
if (finder_options = options.except(:start, :batch_size)).present?
-
ActiveSupport::Deprecation.warn(
-
"Relation#find_in_batches with finder options is deprecated. Please build " \
-
"a scope and then call find_in_batches on it instead."
-
)
-
-
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
-
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
-
-
apply_finder_options(finder_options, true).
-
find_in_batches(options.slice(:start, :batch_size), &block)
-
else
-
super
-
end
-
end
-
-
1
def calculate(operation, column_name, options = {})
-
if options.except(:distinct).present?
-
ActiveSupport::Deprecation.warn(
-
"Relation#calculate with finder options is deprecated. Please build " \
-
"a scope and then call find_in_batches on it instead."
-
)
-
-
apply_finder_options(options.except(:distinct), true)
-
.calculate(operation, column_name, :distinct => options[:distinct])
-
else
-
super
-
end
-
end
-
-
1
def find(*args)
-
options = args.extract_options!
-
-
if options.present?
-
scope = apply_finder_options(options, true)
-
-
case finder = args.first
-
when :first, :last, :all
-
ActiveSupport::Deprecation.warn(
-
"Calling #find(#{finder.inspect}) is deprecated. Please call " \
-
"##{finder} directly instead. You have also used finder options. " \
-
"These are also deprecated. Please build a scope instead of using " \
-
"finder options."
-
)
-
-
scope.send(finder)
-
else
-
ActiveSupport::Deprecation.warn(
-
"Passing options to #find is deprecated. Please build a scope " \
-
"and then call #find on it."
-
)
-
-
scope.find(*args)
-
end
-
else
-
case finder = args.first
-
when :first, :last, :all
-
ActiveSupport::Deprecation.warn(
-
"Calling #find(#{finder.inspect}) is deprecated. Please call " \
-
"##{finder} directly instead."
-
)
-
-
send(finder)
-
else
-
super
-
end
-
end
-
end
-
-
1
def first(*args)
-
if args.empty?
-
super
-
else
-
if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
-
super
-
else
-
ActiveSupport::Deprecation.warn(
-
"Relation#first with finder options is deprecated. Please build " \
-
"a scope and then call #first on it instead."
-
)
-
-
apply_finder_options(args.first, true).first
-
end
-
end
-
end
-
-
1
def last(*args)
-
if args.empty?
-
super
-
else
-
if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
-
super
-
else
-
ActiveSupport::Deprecation.warn(
-
"Relation#last with finder options is deprecated. Please build " \
-
"a scope and then call #last on it instead."
-
)
-
-
apply_finder_options(args.first, true).last
-
end
-
end
-
end
-
-
1
def all(*args)
-
ActiveSupport::Deprecation.warn(
-
"Relation#all is deprecated. If you want to eager-load a relation, you can " \
-
"call #load (e.g. `Post.where(published: true).load`). If you want " \
-
"to get an array of records from a relation, you can call #to_a (e.g. " \
-
"`Post.where(published: true).to_a`)."
-
)
-
apply_finder_options(args.first, true).to_a
-
end
-
-
# ActiveRecord::Relation usually compiled delegator methods at runtime as an optimisation.
-
# However, we want to hook into method_missing in CollectionProxy in order to do extra
-
# stuff around find_or_{create|initialize}_by methods. If a compiled method for the delegation
-
# is defined, then we have no way to hook in because method_missing is never invoked.
-
# Therefore, the purpose of this is simply to prevent the super implementation of
-
# method_missing being called in this case, which prevent the compiled delegation from
-
# being created.
-
1
def method_missing(method, *args, &block)
-
match = DynamicMatchers::Method.match(klass, method)
-
-
if match && match.is_a?(DynamicMatchers::Instantiator)
-
scoping { klass.send(method, *args, &block) }
-
else
-
super
-
end
-
end
-
end
-
-
1
include DeprecatedMethods
-
1
alias_method_chain :update_all, :deprecated_options
-
end
-
end
-
1
require 'arel/crud'
-
1
require 'arel/factory_methods'
-
-
1
require 'arel/expressions'
-
1
require 'arel/predications'
-
1
require 'arel/window_predications'
-
1
require 'arel/math'
-
1
require 'arel/alias_predication'
-
1
require 'arel/order_predications'
-
1
require 'arel/table'
-
1
require 'arel/attributes'
-
1
require 'arel/compatibility/wheres'
-
-
#### these are deprecated
-
1
require 'arel/expression'
-
####
-
-
1
require 'arel/visitors'
-
-
1
require 'arel/tree_manager'
-
1
require 'arel/insert_manager'
-
1
require 'arel/select_manager'
-
1
require 'arel/update_manager'
-
1
require 'arel/delete_manager'
-
1
require 'arel/nodes'
-
-
-
#### these are deprecated
-
1
require 'arel/deprecated'
-
1
require 'arel/sql/engine'
-
1
require 'arel/sql_literal'
-
####
-
-
1
module Arel
-
1
VERSION = '3.0.2'
-
-
1
def self.sql raw_sql
-
Arel::Nodes::SqlLiteral.new raw_sql
-
end
-
-
1
def self.star
-
sql '*'
-
end
-
## Convenience Alias
-
1
Node = Arel::Nodes::Node
-
end
-
1
module Arel
-
1
module AliasPredication
-
1
def as other
-
Nodes::As.new self, Nodes::SqlLiteral.new(other)
-
end
-
end
-
end
-
1
require 'arel/attributes/attribute'
-
-
1
module Arel
-
1
module Attributes
-
###
-
# Factory method to wrap a raw database +column+ to an Arel Attribute.
-
1
def self.for column
-
case column.type
-
when :string, :text, :binary then String
-
when :integer then Integer
-
when :float then Float
-
when :decimal then Decimal
-
when :date, :datetime, :timestamp, :time then Time
-
when :boolean then Boolean
-
else
-
Undefined
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Attributes
-
1
class Attribute < Struct.new :relation, :name
-
1
include Arel::Expressions
-
1
include Arel::Predications
-
1
include Arel::AliasPredication
-
1
include Arel::OrderPredications
-
1
include Arel::Math
-
-
###
-
# Create a node for lowering this attribute
-
1
def lower
-
relation.lower self
-
end
-
end
-
-
1
class String < Attribute; end
-
1
class Time < Attribute; end
-
1
class Boolean < Attribute; end
-
1
class Decimal < Attribute; end
-
1
class Float < Attribute; end
-
1
class Integer < Attribute; end
-
1
class Undefined < Attribute; end
-
end
-
-
1
Attribute = Attributes::Attribute
-
end
-
1
module Arel
-
1
module Compatibility # :nodoc:
-
1
class Wheres # :nodoc:
-
1
include Enumerable
-
-
1
module Value # :nodoc:
-
1
attr_accessor :visitor
-
1
def value
-
visitor.accept self
-
end
-
-
1
def name
-
super.to_sym
-
end
-
end
-
-
1
def initialize engine, collection
-
@engine = engine
-
@collection = collection
-
end
-
-
1
def each
-
to_sql = Visitors::ToSql.new @engine
-
-
@collection.each { |c|
-
c.extend(Value)
-
c.visitor = to_sql
-
yield c
-
}
-
end
-
end
-
end
-
end
-
1
module Arel
-
###
-
# FIXME hopefully we can remove this
-
1
module Crud
-
1
def compile_update values
-
um = UpdateManager.new @engine
-
-
if Nodes::SqlLiteral === values
-
relation = @ctx.from
-
else
-
relation = values.first.first.relation
-
end
-
um.table relation
-
um.set values
-
um.take @ast.limit.expr if @ast.limit
-
um.order(*@ast.orders)
-
um.wheres = @ctx.wheres
-
um
-
end
-
-
# FIXME: this method should go away
-
1
def update values
-
if $VERBOSE
-
warn <<-eowarn
-
update (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-
switch to `compile_update`
-
eowarn
-
end
-
-
um = compile_update values
-
@engine.connection.update um.to_sql, 'AREL'
-
end
-
-
1
def compile_insert values
-
im = create_insert
-
im.insert values
-
im
-
end
-
-
1
def create_insert
-
InsertManager.new @engine
-
end
-
-
# FIXME: this method should go away
-
1
def insert values
-
if $VERBOSE
-
warn <<-eowarn
-
insert (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-
switch to `compile_insert`
-
eowarn
-
end
-
@engine.connection.insert compile_insert(values).to_sql
-
end
-
-
1
def compile_delete
-
dm = DeleteManager.new @engine
-
dm.wheres = @ctx.wheres
-
dm.from @ctx.froms
-
dm
-
end
-
-
1
def delete
-
if $VERBOSE
-
warn <<-eowarn
-
delete (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-
switch to `compile_delete`
-
eowarn
-
end
-
@engine.connection.delete compile_delete.to_sql, 'AREL'
-
end
-
end
-
end
-
1
module Arel
-
1
class DeleteManager < Arel::TreeManager
-
1
def initialize engine
-
super
-
@ast = Nodes::DeleteStatement.new
-
@ctx = @ast
-
end
-
-
1
def from relation
-
@ast.relation = relation
-
self
-
end
-
-
1
def wheres= list
-
@ast.wheres = list
-
end
-
end
-
end
-
1
module Arel
-
1
InnerJoin = Nodes::InnerJoin
-
1
OuterJoin = Nodes::OuterJoin
-
end
-
1
module Arel
-
1
module Expression
-
1
include Arel::OrderPredications
-
end
-
end
-
1
module Arel
-
1
module Expressions
-
1
def count distinct = false
-
Nodes::Count.new [self], distinct
-
end
-
-
1
def sum
-
Nodes::Sum.new [self], Nodes::SqlLiteral.new('sum_id')
-
end
-
-
1
def maximum
-
Nodes::Max.new [self], Nodes::SqlLiteral.new('max_id')
-
end
-
-
1
def minimum
-
Nodes::Min.new [self], Nodes::SqlLiteral.new('min_id')
-
end
-
-
1
def average
-
Nodes::Avg.new [self], Nodes::SqlLiteral.new('avg_id')
-
end
-
-
1
def extract field
-
Nodes::Extract.new [self], field
-
end
-
end
-
end
-
1
module Arel
-
###
-
# Methods for creating various nodes
-
1
module FactoryMethods
-
1
def create_true
-
Arel::Nodes::True.new
-
end
-
-
1
def create_false
-
Arel::Nodes::False.new
-
end
-
-
1
def create_table_alias relation, name
-
Nodes::TableAlias.new(relation, name)
-
end
-
-
1
def create_join to, constraint = nil, klass = Nodes::InnerJoin
-
klass.new(to, constraint)
-
end
-
-
1
def create_string_join to
-
create_join to, nil, Nodes::StringJoin
-
end
-
-
1
def create_and clauses
-
Nodes::And.new clauses
-
end
-
-
1
def create_on expr
-
Nodes::On.new expr
-
end
-
-
1
def grouping expr
-
Nodes::Grouping.new expr
-
end
-
-
###
-
# Create a LOWER() function
-
1
def lower column
-
Nodes::NamedFunction.new 'LOWER', [column]
-
end
-
end
-
end
-
1
module Arel
-
1
class InsertManager < Arel::TreeManager
-
1
def initialize engine
-
super
-
@ast = Nodes::InsertStatement.new
-
end
-
-
1
def into table
-
@ast.relation = table
-
self
-
end
-
-
1
def columns; @ast.columns end
-
1
def values= val; @ast.values = val; end
-
-
1
def insert fields
-
return if fields.empty?
-
-
if String === fields
-
@ast.values = SqlLiteral.new(fields)
-
else
-
@ast.relation ||= fields.first.first.relation
-
-
values = []
-
-
fields.each do |column, value|
-
@ast.columns << column
-
values << value
-
end
-
@ast.values = create_values values, @ast.columns
-
end
-
end
-
-
1
def create_values values, columns
-
Nodes::Values.new values, columns
-
end
-
end
-
end
-
1
module Arel
-
1
module Math
-
1
def *(other)
-
Arel::Nodes::Multiplication.new(self, other)
-
end
-
-
1
def +(other)
-
Arel::Nodes::Grouping.new(Arel::Nodes::Addition.new(self, other))
-
end
-
-
1
def -(other)
-
Arel::Nodes::Grouping.new(Arel::Nodes::Subtraction.new(self, other))
-
end
-
-
1
def /(other)
-
Arel::Nodes::Division.new(self, other)
-
end
-
end
-
end
-
# node
-
1
require 'arel/nodes/node'
-
1
require 'arel/nodes/select_statement'
-
1
require 'arel/nodes/select_core'
-
1
require 'arel/nodes/insert_statement'
-
1
require 'arel/nodes/update_statement'
-
-
# terminal
-
-
1
require 'arel/nodes/terminal'
-
1
require 'arel/nodes/true'
-
1
require 'arel/nodes/false'
-
-
# unary
-
1
require 'arel/nodes/unary'
-
1
require 'arel/nodes/grouping'
-
1
require 'arel/nodes/ascending'
-
1
require 'arel/nodes/descending'
-
1
require 'arel/nodes/unqualified_column'
-
1
require 'arel/nodes/with'
-
-
# binary
-
1
require 'arel/nodes/binary'
-
1
require 'arel/nodes/equality'
-
1
require 'arel/nodes/in' # Why is this subclassed from equality?
-
1
require 'arel/nodes/join_source'
-
1
require 'arel/nodes/delete_statement'
-
1
require 'arel/nodes/table_alias'
-
1
require 'arel/nodes/infix_operation'
-
1
require 'arel/nodes/over'
-
-
# nary
-
1
require 'arel/nodes/and'
-
-
# function
-
# FIXME: Function + Alias can be rewritten as a Function and Alias node.
-
# We should make Function a Unary node and deprecate the use of "aliaz"
-
1
require 'arel/nodes/function'
-
1
require 'arel/nodes/count'
-
1
require 'arel/nodes/extract'
-
1
require 'arel/nodes/values'
-
1
require 'arel/nodes/named_function'
-
-
# windows
-
1
require 'arel/nodes/window'
-
-
# joins
-
1
require 'arel/nodes/inner_join'
-
1
require 'arel/nodes/outer_join'
-
1
require 'arel/nodes/string_join'
-
-
1
require 'arel/nodes/sql_literal'
-
1
module Arel
-
1
module Nodes
-
1
class And < Arel::Nodes::Node
-
1
attr_reader :children
-
-
1
def initialize children, right = nil
-
unless Array === children
-
warn "(#{caller.first}) AND nodes should be created with a list"
-
children = [children, right]
-
end
-
@children = children
-
end
-
-
1
def left
-
children.first
-
end
-
-
1
def right
-
children[1]
-
end
-
-
1
def hash
-
children.hash
-
end
-
-
1
def eql? other
-
self.class == other.class &&
-
self.children == other.children
-
end
-
1
alias :== :eql?
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Ascending < Ordering
-
-
1
def reverse
-
Descending.new(expr)
-
end
-
-
1
def direction
-
:asc
-
end
-
-
1
def ascending?
-
true
-
end
-
-
1
def descending?
-
false
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Binary < Arel::Nodes::Node
-
1
attr_accessor :left, :right
-
-
1
def initialize left, right
-
@left = left
-
@right = right
-
end
-
-
1
def initialize_copy other
-
super
-
@left = @left.clone if @left
-
@right = @right.clone if @right
-
end
-
-
1
def hash
-
[@left, @right].hash
-
end
-
-
1
def eql? other
-
self.class == other.class &&
-
self.left == other.left &&
-
self.right == other.right
-
end
-
1
alias :== :eql?
-
end
-
-
%w{
-
As
-
1
Assignment
-
Between
-
DoesNotMatch
-
GreaterThan
-
GreaterThanOrEqual
-
Join
-
LessThan
-
LessThanOrEqual
-
Matches
-
NotEqual
-
NotIn
-
Or
-
Union
-
UnionAll
-
Intersect
-
Except
-
}.each do |name|
-
17
const_set name, Class.new(Binary)
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Count < Arel::Nodes::Function
-
1
def initialize expr, distinct = false, aliaz = nil
-
super(expr, aliaz)
-
@distinct = distinct
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class DeleteStatement < Arel::Nodes::Binary
-
1
alias :relation :left
-
1
alias :relation= :left=
-
1
alias :wheres :right
-
1
alias :wheres= :right=
-
-
1
def initialize relation = nil, wheres = []
-
super
-
end
-
-
1
def initialize_copy other
-
super
-
@right = @right.clone
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Descending < Ordering
-
-
1
def reverse
-
Ascending.new(expr)
-
end
-
-
1
def direction
-
:desc
-
end
-
-
1
def ascending?
-
false
-
end
-
-
1
def descending?
-
true
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Equality < Arel::Nodes::Binary
-
1
def operator; :== end
-
1
alias :operand1 :left
-
1
alias :operand2 :right
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
-
1
class Extract < Arel::Nodes::Unary
-
1
include Arel::Expression
-
1
include Arel::Predications
-
-
1
attr_accessor :field
-
1
attr_accessor :alias
-
-
1
def initialize expr, field, aliaz = nil
-
super(expr)
-
@field = field
-
@alias = aliaz && SqlLiteral.new(aliaz)
-
end
-
-
1
def as aliaz
-
self.alias = SqlLiteral.new(aliaz)
-
self
-
end
-
-
1
def hash
-
super ^ [@field, @alias].hash
-
end
-
-
1
def eql? other
-
super &&
-
self.field == other.field &&
-
self.alias == other.alias
-
end
-
1
alias :== :eql?
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class False < Arel::Nodes::Node
-
1
def hash
-
self.class.hash
-
end
-
-
1
def eql? other
-
self.class == other.class
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Function < Arel::Nodes::Node
-
1
include Arel::Expression
-
1
include Arel::Predications
-
1
include Arel::WindowPredications
-
1
attr_accessor :expressions, :alias, :distinct
-
-
1
def initialize expr, aliaz = nil
-
@expressions = expr
-
@alias = aliaz && SqlLiteral.new(aliaz)
-
@distinct = false
-
end
-
-
1
def as aliaz
-
self.alias = SqlLiteral.new(aliaz)
-
self
-
end
-
-
1
def hash
-
[@expressions, @alias, @distinct].hash
-
end
-
-
1
def eql? other
-
self.class == other.class &&
-
self.expressions == other.expressions &&
-
self.alias == other.alias &&
-
self.distinct == other.distinct
-
end
-
end
-
-
%w{
-
Sum
-
1
Exists
-
Max
-
Min
-
Avg
-
}.each do |name|
-
5
const_set(name, Class.new(Function))
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Grouping < Unary
-
1
include Arel::Predications
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class In < Equality
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
-
1
class InfixOperation < Binary
-
1
include Arel::Expressions
-
1
include Arel::Predications
-
1
include Arel::OrderPredications
-
1
include Arel::AliasPredication
-
1
include Arel::Math
-
-
1
attr_reader :operator
-
-
1
def initialize operator, left, right
-
super(left, right)
-
@operator = operator
-
end
-
end
-
-
1
class Multiplication < InfixOperation
-
1
def initialize left, right
-
super(:*, left, right)
-
end
-
end
-
-
1
class Division < InfixOperation
-
1
def initialize left, right
-
super(:/, left, right)
-
end
-
end
-
-
1
class Addition < InfixOperation
-
1
def initialize left, right
-
super(:+, left, right)
-
end
-
end
-
-
1
class Subtraction < InfixOperation
-
1
def initialize left, right
-
super(:-, left, right)
-
end
-
end
-
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class InnerJoin < Arel::Nodes::Join
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class InsertStatement < Arel::Nodes::Node
-
1
attr_accessor :relation, :columns, :values
-
-
1
def initialize
-
@relation = nil
-
@columns = []
-
@values = nil
-
end
-
-
1
def initialize_copy other
-
super
-
@columns = @columns.clone
-
@values = @values.clone if @values
-
end
-
-
1
def hash
-
[@relation, @columns, @values].hash
-
end
-
-
1
def eql? other
-
self.class == other.class &&
-
self.relation == other.relation &&
-
self.columns == other.columns &&
-
self.values == other.values
-
end
-
1
alias :== :eql?
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
###
-
# Class that represents a join source
-
#
-
# http://www.sqlite.org/syntaxdiagrams.html#join-source
-
-
1
class JoinSource < Arel::Nodes::Binary
-
1
def initialize single_source, joinop = []
-
super
-
end
-
-
1
def empty?
-
!left && right.empty?
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class NamedFunction < Arel::Nodes::Function
-
1
attr_accessor :name
-
-
1
def initialize name, expr, aliaz = nil
-
super(expr, aliaz)
-
@name = name
-
end
-
-
1
def hash
-
super ^ @name.hash
-
end
-
-
1
def eql? other
-
super && self.name == other.name
-
end
-
1
alias :== :eql?
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
###
-
# Abstract base class for all AST nodes
-
1
class Node
-
1
include Arel::FactoryMethods
-
1
include Enumerable
-
-
###
-
# Factory method to create a Nodes::Not node that has the recipient of
-
# the caller as a child.
-
1
def not
-
Nodes::Not.new self
-
end
-
-
###
-
# Factory method to create a Nodes::Grouping node that has an Nodes::Or
-
# node as a child.
-
1
def or right
-
Nodes::Grouping.new Nodes::Or.new(self, right)
-
end
-
-
###
-
# Factory method to create an Nodes::And node.
-
1
def and right
-
Nodes::And.new [self, right]
-
end
-
-
# FIXME: this method should go away. I don't like people calling
-
# to_sql on non-head nodes. This forces us to walk the AST until we
-
# can find a node that has a "relation" member.
-
#
-
# Maybe we should just use `Table.engine`? :'(
-
1
def to_sql engine = Table.engine
-
engine.connection.visitor.accept self
-
end
-
-
# Iterate through AST, nodes will be yielded depth-first
-
1
def each &block
-
return enum_for(:each) unless block_given?
-
-
::Arel::Visitors::DepthFirst.new(block).accept self
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class OuterJoin < Arel::Nodes::Join
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
-
1
class Over < Binary
-
1
include Arel::AliasPredication
-
-
1
def initialize(left, right = nil)
-
super(left, right)
-
end
-
-
1
def operator; 'OVER' end
-
end
-
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class SelectCore < Arel::Nodes::Node
-
1
attr_accessor :top, :projections, :wheres, :groups, :windows
-
1
attr_accessor :having, :source, :set_quantifier
-
-
1
def initialize
-
@source = JoinSource.new nil
-
@top = nil
-
-
# http://savage.net.au/SQL/sql-92.bnf.html#set%20quantifier
-
@set_quantifier = nil
-
@projections = []
-
@wheres = []
-
@groups = []
-
@having = nil
-
@windows = []
-
end
-
-
1
def from
-
@source.left
-
end
-
-
1
def from= value
-
@source.left = value
-
end
-
-
1
alias :froms= :from=
-
1
alias :froms :from
-
-
1
def initialize_copy other
-
super
-
@source = @source.clone if @source
-
@projections = @projections.clone
-
@wheres = @wheres.clone
-
@groups = @groups.clone
-
@having = @having.clone if @having
-
@windows = @windows.clone
-
end
-
-
1
def hash
-
[
-
@source, @top, @set_quantifier, @projections,
-
@wheres, @groups, @having, @windows
-
].hash
-
end
-
-
1
def eql? other
-
self.class == other.class &&
-
self.source == other.source &&
-
self.top == other.top &&
-
self.set_quantifier == other.set_quantifier &&
-
self.projections == other.projections &&
-
self.wheres == other.wheres &&
-
self.groups == other.groups &&
-
self.having == other.having &&
-
self.windows == other.windows
-
end
-
1
alias :== :eql?
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class SelectStatement < Arel::Nodes::Node
-
1
attr_reader :cores
-
1
attr_accessor :limit, :orders, :lock, :offset, :with
-
-
1
def initialize cores = [SelectCore.new]
-
@cores = cores
-
@orders = []
-
@limit = nil
-
@lock = nil
-
@offset = nil
-
@with = nil
-
end
-
-
1
def initialize_copy other
-
super
-
@cores = @cores.map { |x| x.clone }
-
@orders = @orders.map { |x| x.clone }
-
end
-
-
1
def hash
-
[@cores, @orders, @limit, @lock, @offset, @with].hash
-
end
-
-
1
def eql? other
-
self.class == other.class &&
-
self.cores == other.cores &&
-
self.orders == other.orders &&
-
self.limit == other.limit &&
-
self.lock == other.lock &&
-
self.offset == other.offset &&
-
self.with == other.with
-
end
-
1
alias :== :eql?
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class SqlLiteral < String
-
1
include Arel::Expressions
-
1
include Arel::Predications
-
1
include Arel::AliasPredication
-
1
include Arel::OrderPredications
-
end
-
-
1
class BindParam < SqlLiteral
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class StringJoin < Arel::Nodes::Join
-
1
def initialize left, right = nil
-
super
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class TableAlias < Arel::Nodes::Binary
-
1
alias :name :right
-
1
alias :relation :left
-
1
alias :table_alias :name
-
-
1
def [] name
-
Attribute.new(self, name)
-
end
-
-
1
def table_name
-
relation.respond_to?(:name) ? relation.name : name
-
end
-
-
1
def engine
-
relation.engine
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Distinct < Arel::Nodes::Node
-
1
def hash
-
self.class.hash
-
end
-
-
1
def eql? other
-
self.class == other.class
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class True < Arel::Nodes::Node
-
1
def hash
-
self.class.hash
-
end
-
-
1
def eql? other
-
self.class == other.class
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Unary < Arel::Nodes::Node
-
1
attr_accessor :expr
-
1
alias :value :expr
-
-
1
def initialize expr
-
@expr = expr
-
end
-
-
1
def hash
-
@expr.hash
-
end
-
-
1
def eql? other
-
self.class == other.class &&
-
self.expr == other.expr
-
end
-
1
alias :== :eql?
-
end
-
-
%w{
-
Bin
-
1
Group
-
Having
-
Limit
-
Not
-
Offset
-
On
-
Ordering
-
Top
-
Lock
-
DistinctOn
-
}.each do |name|
-
11
const_set(name, Class.new(Unary))
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class UnqualifiedColumn < Arel::Nodes::Unary
-
1
alias :attribute :expr
-
1
alias :attribute= :expr=
-
-
1
def relation
-
@expr.relation
-
end
-
-
1
def column
-
@expr.column
-
end
-
-
1
def name
-
@expr.name
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class UpdateStatement < Arel::Nodes::Node
-
1
attr_accessor :relation, :wheres, :values, :orders, :limit
-
1
attr_accessor :key
-
-
1
def initialize
-
@relation = nil
-
@wheres = []
-
@values = []
-
@orders = []
-
@limit = nil
-
@key = nil
-
end
-
-
1
def initialize_copy other
-
super
-
@wheres = @wheres.clone
-
@values = @values.clone
-
end
-
-
1
def hash
-
[@relation, @wheres, @values, @orders, @limit, @key].hash
-
end
-
-
1
def eql? other
-
self.class == other.class &&
-
self.relation == other.relation &&
-
self.wheres == other.wheres &&
-
self.values == other.values &&
-
self.orders == other.orders &&
-
self.limit == other.limit &&
-
self.key == other.key
-
end
-
1
alias :== :eql?
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Values < Arel::Nodes::Binary
-
1
alias :expressions :left
-
1
alias :expressions= :left=
-
1
alias :columns :right
-
1
alias :columns= :right=
-
-
1
def initialize exprs, columns = []
-
super
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class With < Arel::Nodes::Unary
-
1
alias children expr
-
end
-
-
1
class WithRecursive < With; end
-
end
-
end
-
-
1
module Arel
-
1
module OrderPredications
-
-
1
def asc
-
Nodes::Ascending.new self
-
end
-
-
1
def desc
-
Nodes::Descending.new self
-
end
-
-
end
-
end
-
1
module Arel
-
1
module Predications
-
1
def not_eq other
-
Nodes::NotEqual.new self, other
-
end
-
-
1
def not_eq_any others
-
grouping_any :not_eq, others
-
end
-
-
1
def not_eq_all others
-
grouping_all :not_eq, others
-
end
-
-
1
def eq other
-
Nodes::Equality.new self, other
-
end
-
-
1
def eq_any others
-
grouping_any :eq, others
-
end
-
-
1
def eq_all others
-
grouping_all :eq, others
-
end
-
-
1
def in other
-
case other
-
when Arel::SelectManager
-
Arel::Nodes::In.new(self, other.ast)
-
when Range
-
if other.exclude_end?
-
left = Nodes::GreaterThanOrEqual.new(self, other.begin)
-
right = Nodes::LessThan.new(self, other.end)
-
Nodes::And.new [left, right]
-
else
-
Nodes::Between.new(self, Nodes::And.new([other.begin, other.end]))
-
end
-
else
-
Nodes::In.new self, other
-
end
-
end
-
-
1
def in_any others
-
grouping_any :in, others
-
end
-
-
1
def in_all others
-
grouping_all :in, others
-
end
-
-
1
def not_in other
-
case other
-
when Arel::SelectManager
-
Arel::Nodes::NotIn.new(self, other.ast)
-
when Range
-
if other.exclude_end?
-
left = Nodes::LessThan.new(self, other.begin)
-
right = Nodes::GreaterThanOrEqual.new(self, other.end)
-
Nodes::Or.new left, right
-
else
-
left = Nodes::LessThan.new(self, other.begin)
-
right = Nodes::GreaterThan.new(self, other.end)
-
Nodes::Or.new left, right
-
end
-
else
-
Nodes::NotIn.new self, other
-
end
-
end
-
-
1
def not_in_any others
-
grouping_any :not_in, others
-
end
-
-
1
def not_in_all others
-
grouping_all :not_in, others
-
end
-
-
1
def matches other
-
Nodes::Matches.new self, other
-
end
-
-
1
def matches_any others
-
grouping_any :matches, others
-
end
-
-
1
def matches_all others
-
grouping_all :matches, others
-
end
-
-
1
def does_not_match other
-
Nodes::DoesNotMatch.new self, other
-
end
-
-
1
def does_not_match_any others
-
grouping_any :does_not_match, others
-
end
-
-
1
def does_not_match_all others
-
grouping_all :does_not_match, others
-
end
-
-
1
def gteq right
-
Nodes::GreaterThanOrEqual.new self, right
-
end
-
-
1
def gteq_any others
-
grouping_any :gteq, others
-
end
-
-
1
def gteq_all others
-
grouping_all :gteq, others
-
end
-
-
1
def gt right
-
Nodes::GreaterThan.new self, right
-
end
-
-
1
def gt_any others
-
grouping_any :gt, others
-
end
-
-
1
def gt_all others
-
grouping_all :gt, others
-
end
-
-
1
def lt right
-
Nodes::LessThan.new self, right
-
end
-
-
1
def lt_any others
-
grouping_any :lt, others
-
end
-
-
1
def lt_all others
-
grouping_all :lt, others
-
end
-
-
1
def lteq right
-
Nodes::LessThanOrEqual.new self, right
-
end
-
-
1
def lteq_any others
-
grouping_any :lteq, others
-
end
-
-
1
def lteq_all others
-
grouping_all :lteq, others
-
end
-
-
1
private
-
-
1
def grouping_any method_id, others
-
nodes = others.map {|expr| send(method_id, expr)}
-
Nodes::Grouping.new nodes.inject { |memo,node|
-
Nodes::Or.new(memo, node)
-
}
-
end
-
-
1
def grouping_all method_id, others
-
Nodes::Grouping.new Nodes::And.new(others.map {|expr| send(method_id, expr)})
-
end
-
end
-
end
-
1
module Arel
-
1
class SelectManager < Arel::TreeManager
-
1
include Arel::Crud
-
-
1
def initialize engine, table = nil
-
super(engine)
-
@ast = Nodes::SelectStatement.new
-
@ctx = @ast.cores.last
-
from table
-
end
-
-
1
def initialize_copy other
-
super
-
@ctx = @ast.cores.last
-
end
-
-
1
def limit
-
@ast.limit && @ast.limit.expr
-
end
-
1
alias :taken :limit
-
-
1
def constraints
-
@ctx.wheres
-
end
-
-
1
def offset
-
@ast.offset && @ast.offset.expr
-
end
-
-
1
def skip amount
-
if amount
-
@ast.offset = Nodes::Offset.new(amount)
-
else
-
@ast.offset = nil
-
end
-
self
-
end
-
1
alias :offset= :skip
-
-
###
-
# Produces an Arel::Nodes::Exists node
-
1
def exists
-
Arel::Nodes::Exists.new @ast
-
end
-
-
1
def as other
-
create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other)
-
end
-
-
1
def where_clauses
-
if $VERBOSE
-
warn "(#{caller.first}) where_clauses is deprecated and will be removed in arel 4.0.0 with no replacement"
-
end
-
to_sql = Visitors::ToSql.new @engine.connection
-
@ctx.wheres.map { |c| to_sql.accept c }
-
end
-
-
1
def lock locking = Arel.sql('FOR UPDATE')
-
case locking
-
when true
-
locking = Arel.sql('FOR UPDATE')
-
when Arel::Nodes::SqlLiteral
-
when String
-
locking = Arel.sql locking
-
end
-
-
@ast.lock = Nodes::Lock.new(locking)
-
self
-
end
-
-
1
def locked
-
@ast.lock
-
end
-
-
1
def on *exprs
-
@ctx.source.right.last.right = Nodes::On.new(collapse(exprs))
-
self
-
end
-
-
1
def group *columns
-
columns.each do |column|
-
# FIXME: backwards compat
-
column = Nodes::SqlLiteral.new(column) if String === column
-
column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
-
-
@ctx.groups.push Nodes::Group.new column
-
end
-
self
-
end
-
-
1
def from table
-
table = Nodes::SqlLiteral.new(table) if String === table
-
# FIXME: this is a hack to support
-
# test_with_two_tables_in_from_without_getting_double_quoted
-
# from the AR tests.
-
-
case table
-
when Nodes::Join
-
@ctx.source.right << table
-
else
-
@ctx.source.left = table
-
end
-
-
self
-
end
-
-
1
def froms
-
@ast.cores.map { |x| x.from }.compact
-
end
-
-
1
def join relation, klass = Nodes::InnerJoin
-
return self unless relation
-
-
case relation
-
when String, Nodes::SqlLiteral
-
raise if relation.blank?
-
klass = Nodes::StringJoin
-
end
-
-
@ctx.source.right << create_join(relation, nil, klass)
-
self
-
end
-
-
1
def having *exprs
-
@ctx.having = Nodes::Having.new(collapse(exprs, @ctx.having))
-
self
-
end
-
-
1
def window name
-
window = Nodes::NamedWindow.new(name)
-
@ctx.windows.push window
-
window
-
end
-
-
1
def project *projections
-
# FIXME: converting these to SQLLiterals is probably not good, but
-
# rails tests require it.
-
@ctx.projections.concat projections.map { |x|
-
[Symbol, String].include?(x.class) ? SqlLiteral.new(x.to_s) : x
-
}
-
self
-
end
-
-
1
def projections
-
@ctx.projections
-
end
-
-
1
def projections= projections
-
@ctx.projections = projections
-
end
-
-
1
def distinct(value = true)
-
if value
-
@ctx.set_quantifier = Arel::Nodes::Distinct.new
-
else
-
@ctx.set_quantifier = nil
-
end
-
end
-
-
1
def order *expr
-
# FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
-
@ast.orders.concat expr.map { |x|
-
String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
-
}
-
self
-
end
-
-
1
def orders
-
@ast.orders
-
end
-
-
1
def wheres
-
warn "#{caller[0]}: SelectManager#wheres is deprecated and will be removed in ARel 4.0.0 with no replacement"
-
Compatibility::Wheres.new @engine.connection, @ctx.wheres
-
end
-
-
1
def where_sql
-
return if @ctx.wheres.empty?
-
-
viz = Visitors::WhereSql.new @engine.connection
-
Nodes::SqlLiteral.new viz.accept @ctx
-
end
-
-
1
def union operation, other = nil
-
if other
-
node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
-
else
-
other = operation
-
node_class = Nodes::Union
-
end
-
-
node_class.new self.ast, other.ast
-
end
-
-
1
def intersect other
-
Nodes::Intersect.new ast, other.ast
-
end
-
-
1
def except other
-
Nodes::Except.new ast, other.ast
-
end
-
1
alias :minus :except
-
-
1
def with *subqueries
-
if subqueries.first.is_a? Symbol
-
node_class = Nodes.const_get("With#{subqueries.shift.to_s.capitalize}")
-
else
-
node_class = Nodes::With
-
end
-
@ast.with = node_class.new(subqueries.flatten)
-
-
self
-
end
-
-
1
def take limit
-
if limit
-
@ast.limit = Nodes::Limit.new(limit)
-
@ctx.top = Nodes::Top.new(limit)
-
else
-
@ast.limit = nil
-
@ctx.top = nil
-
end
-
self
-
end
-
1
alias limit= take
-
-
1
def join_sql
-
return nil if @ctx.source.right.empty?
-
-
sql = visitor.dup.extend(Visitors::JoinSql).accept @ctx
-
Nodes::SqlLiteral.new sql
-
end
-
-
1
def order_clauses
-
visitor = Visitors::OrderClauses.new(@engine.connection)
-
visitor.accept(@ast).map { |x|
-
Nodes::SqlLiteral.new x
-
}
-
end
-
-
1
def join_sources
-
@ctx.source.right
-
end
-
-
1
def source
-
@ctx.source
-
end
-
-
1
def joins manager
-
if $VERBOSE
-
warn "joins is deprecated and will be removed in 4.0.0"
-
warn "please remove your call to joins from #{caller.first}"
-
end
-
manager.join_sql
-
end
-
-
1
class Row < Struct.new(:data) # :nodoc:
-
1
def id
-
data['id']
-
end
-
-
1
def method_missing(name, *args)
-
name = name.to_s
-
return data[name] if data.key?(name)
-
super
-
end
-
end
-
-
1
def to_a # :nodoc:
-
warn "to_a is deprecated. Please remove it from #{caller[0]}"
-
# FIXME: I think `select` should be made public...
-
@engine.connection.send(:select, to_sql, 'AREL').map { |x| Row.new(x) }
-
end
-
-
# FIXME: this method should go away
-
1
def insert values
-
if $VERBOSE
-
warn <<-eowarn
-
insert (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-
switch to `compile_insert`
-
eowarn
-
end
-
-
im = compile_insert(values)
-
table = @ctx.froms
-
-
primary_key = table.primary_key
-
primary_key_name = primary_key.name if primary_key
-
-
# FIXME: in AR tests values sometimes were Array and not Hash therefore is_a?(Hash) check is added
-
primary_key_value = primary_key && values.is_a?(Hash) && values[primary_key]
-
im.into table
-
# Oracle adapter needs primary key name to generate RETURNING ... INTO ... clause
-
# for tables which assign primary key value using trigger.
-
# RETURNING ... INTO ... clause will be added only if primary_key_value is nil
-
# therefore it is necessary to pass primary key value as well
-
@engine.connection.insert im.to_sql, 'AREL', primary_key_name, primary_key_value
-
end
-
-
1
private
-
1
def collapse exprs, existing = nil
-
exprs = exprs.unshift(existing.expr) if existing
-
exprs = exprs.compact.map { |expr|
-
if String === expr
-
# FIXME: Don't do this automatically
-
Arel.sql(expr)
-
else
-
expr
-
end
-
}
-
-
if exprs.length == 1
-
exprs.first
-
else
-
create_and exprs
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Sql
-
1
class Engine
-
1
def self.new thing
-
#warn "#{caller.first} -- Engine will be removed"
-
thing
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
class SqlLiteral < Nodes::SqlLiteral
-
end
-
end
-
1
module Arel
-
1
class Table
-
1
include Arel::Crud
-
1
include Arel::FactoryMethods
-
-
1
@engine = nil
-
2
class << self; attr_accessor :engine; end
-
-
1
attr_accessor :name, :engine, :aliases, :table_alias
-
-
# TableAlias and Table both have a #table_name which is the name of the underlying table
-
1
alias :table_name :name
-
-
1
def initialize name, engine = Table.engine
-
1
@name = name.to_s
-
1
@engine = engine
-
1
@columns = nil
-
1
@aliases = []
-
1
@table_alias = nil
-
1
@primary_key = nil
-
-
1
if Hash === engine
-
@engine = engine[:engine] || Table.engine
-
-
# Sometime AR sends an :as parameter to table, to let the table know
-
# that it is an Alias. We may want to override new, and return a
-
# TableAlias node?
-
@table_alias = engine[:as] unless engine[:as].to_s == @name
-
end
-
end
-
-
1
def primary_key
-
if $VERBOSE
-
warn <<-eowarn
-
primary_key (#{caller.first}) is deprecated and will be removed in ARel 4.0.0
-
eowarn
-
end
-
@primary_key ||= begin
-
primary_key_name = @engine.connection.primary_key(name)
-
# some tables might be without primary key
-
primary_key_name && self[primary_key_name]
-
end
-
end
-
-
1
def alias name = "#{self.name}_2"
-
Nodes::TableAlias.new(self, name).tap do |node|
-
@aliases << node
-
end
-
end
-
-
1
def from table
-
SelectManager.new(@engine, table)
-
end
-
-
1
def joins manager
-
if $VERBOSE
-
warn "joins is deprecated and will be removed in 4.0.0"
-
warn "please remove your call to joins from #{caller.first}"
-
end
-
nil
-
end
-
-
1
def join relation, klass = Nodes::InnerJoin
-
return from(self) unless relation
-
-
case relation
-
when String, Nodes::SqlLiteral
-
raise if relation.blank?
-
klass = Nodes::StringJoin
-
end
-
-
from(self).join(relation, klass)
-
end
-
-
1
def group *columns
-
from(self).group(*columns)
-
end
-
-
1
def order *expr
-
from(self).order(*expr)
-
end
-
-
1
def where condition
-
from(self).where condition
-
end
-
-
1
def project *things
-
from(self).project(*things)
-
end
-
-
1
def take amount
-
from(self).take amount
-
end
-
-
1
def skip amount
-
from(self).skip amount
-
end
-
-
1
def having expr
-
from(self).having expr
-
end
-
-
1
def columns
-
if $VERBOSE
-
warn <<-eowarn
-
(#{caller.first}) Arel::Table#columns is deprecated and will be removed in
-
Arel 4.0.0 with no replacement. PEW PEW PEW!!!
-
eowarn
-
end
-
@columns ||=
-
attributes_for @engine.connection.columns(@name, "#{@name} Columns")
-
end
-
-
1
def [] name
-
::Arel::Attribute.new self, name
-
end
-
-
1
def select_manager
-
SelectManager.new(@engine)
-
end
-
-
1
def insert_manager
-
InsertManager.new(@engine)
-
end
-
-
1
def hash
-
[@name, @engine, @aliases, @table_alias].hash
-
end
-
-
1
def eql? other
-
self.class == other.class &&
-
self.name == other.name &&
-
self.engine == other.engine &&
-
self.aliases == other.aliases &&
-
self.table_alias == other.table_alias
-
end
-
1
alias :== :eql?
-
-
1
private
-
-
1
def attributes_for columns
-
return nil unless columns
-
-
columns.map do |column|
-
Attributes.for(column).new self, column.name.to_sym
-
end
-
end
-
-
1
@@table_cache = nil
-
1
def self.table_cache engine # :nodoc:
-
if $VERBOSE
-
warn <<-eowarn
-
(#{caller.first}) Arel::Table.table_cache is deprecated and will be removed in
-
Arel 4.0.0 with no replacement. PEW PEW PEW!!!
-
eowarn
-
end
-
@@table_cache ||= Hash[engine.connection.tables.map { |x| [x,true] }]
-
end
-
end
-
end
-
1
module Arel
-
1
class TreeManager
-
1
include Arel::FactoryMethods
-
-
1
attr_reader :ast, :engine
-
-
1
def initialize engine
-
@engine = engine
-
@ctx = nil
-
end
-
-
1
def to_dot
-
Visitors::Dot.new.accept @ast
-
end
-
-
1
def visitor
-
engine.connection.visitor
-
end
-
-
1
def to_sql
-
visitor.accept @ast
-
end
-
-
1
def initialize_copy other
-
super
-
@ast = @ast.clone
-
end
-
-
1
def where expr
-
if Arel::TreeManager === expr
-
expr = expr.ast
-
end
-
@ctx.wheres << expr
-
self
-
end
-
end
-
end
-
1
module Arel
-
1
class UpdateManager < Arel::TreeManager
-
1
def initialize engine
-
super
-
@ast = Nodes::UpdateStatement.new
-
@ctx = @ast
-
end
-
-
1
def take limit
-
@ast.limit = Nodes::Limit.new(limit) if limit
-
self
-
end
-
-
1
def key= key
-
@ast.key = key
-
end
-
-
1
def key
-
@ast.key
-
end
-
-
1
def order *expr
-
@ast.orders = expr
-
self
-
end
-
-
###
-
# UPDATE +table+
-
1
def table table
-
@ast.relation = table
-
self
-
end
-
-
1
def wheres= exprs
-
@ast.wheres = exprs
-
end
-
-
1
def where expr
-
@ast.wheres << expr
-
self
-
end
-
-
1
def set values
-
if String === values
-
@ast.values = [values]
-
else
-
@ast.values = values.map { |column,value|
-
Nodes::Assignment.new(
-
Nodes::UnqualifiedColumn.new(column),
-
value
-
)
-
}
-
end
-
self
-
end
-
end
-
end
-
1
require 'arel/visitors/visitor'
-
1
require 'arel/visitors/depth_first'
-
1
require 'arel/visitors/to_sql'
-
1
require 'arel/visitors/sqlite'
-
1
require 'arel/visitors/postgresql'
-
1
require 'arel/visitors/mysql'
-
1
require 'arel/visitors/mssql'
-
1
require 'arel/visitors/oracle'
-
1
require 'arel/visitors/join_sql'
-
1
require 'arel/visitors/where_sql'
-
1
require 'arel/visitors/order_clauses'
-
1
require 'arel/visitors/dot'
-
1
require 'arel/visitors/ibm_db'
-
1
require 'arel/visitors/informix'
-
-
1
module Arel
-
1
module Visitors
-
1
VISITORS = {
-
'postgresql' => Arel::Visitors::PostgreSQL,
-
'mysql' => Arel::Visitors::MySQL,
-
'mysql2' => Arel::Visitors::MySQL,
-
'mssql' => Arel::Visitors::MSSQL,
-
'sqlserver' => Arel::Visitors::MSSQL,
-
'oracle_enhanced' => Arel::Visitors::Oracle,
-
'sqlite' => Arel::Visitors::SQLite,
-
'sqlite3' => Arel::Visitors::SQLite,
-
'ibm_db' => Arel::Visitors::IBM_DB,
-
'informix' => Arel::Visitors::Informix,
-
}
-
-
1
ENGINE_VISITORS = Hash.new do |hash, engine|
-
pool = engine.connection_pool
-
adapter = pool.spec.config[:adapter]
-
hash[engine] = (VISITORS[adapter] || Visitors::ToSql).new(engine)
-
end
-
-
1
def self.visitor_for engine
-
ENGINE_VISITORS[engine]
-
end
-
2
class << self; alias :for :visitor_for; end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
module BindVisitor
-
1
def initialize target
-
@block = nil
-
super
-
end
-
-
1
def accept node, &block
-
@block = block if block_given?
-
super
-
end
-
-
1
private
-
1
def visit_Arel_Nodes_BindParam o
-
if @block
-
@block.call
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class Dot < Arel::Visitors::Visitor
-
1
class Node # :nodoc:
-
1
attr_accessor :name, :id, :fields
-
-
1
def initialize name, id, fields = []
-
@name = name
-
@id = id
-
@fields = fields
-
end
-
end
-
-
1
class Edge < Struct.new :name, :from, :to # :nodoc:
-
end
-
-
1
def initialize
-
@nodes = []
-
@edges = []
-
@node_stack = []
-
@edge_stack = []
-
@seen = {}
-
end
-
-
1
def accept object
-
super
-
to_dot
-
end
-
-
1
private
-
1
def visit_Arel_Nodes_Ordering o
-
visit_edge o, "expr"
-
end
-
-
1
def visit_Arel_Nodes_TableAlias o
-
visit_edge o, "name"
-
visit_edge o, "relation"
-
end
-
-
1
def visit_Arel_Nodes_Count o
-
visit_edge o, "expressions"
-
visit_edge o, "distinct"
-
end
-
-
1
def visit_Arel_Nodes_Values o
-
visit_edge o, "expressions"
-
end
-
-
1
def visit_Arel_Nodes_StringJoin o
-
visit_edge o, "left"
-
end
-
-
1
def visit_Arel_Nodes_InnerJoin o
-
visit_edge o, "left"
-
visit_edge o, "right"
-
end
-
1
alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin
-
-
1
def visit_Arel_Nodes_DeleteStatement o
-
visit_edge o, "relation"
-
visit_edge o, "wheres"
-
end
-
-
1
def unary o
-
visit_edge o, "expr"
-
end
-
1
alias :visit_Arel_Nodes_Group :unary
-
1
alias :visit_Arel_Nodes_BindParam :unary
-
1
alias :visit_Arel_Nodes_Grouping :unary
-
1
alias :visit_Arel_Nodes_Having :unary
-
1
alias :visit_Arel_Nodes_Limit :unary
-
1
alias :visit_Arel_Nodes_Not :unary
-
1
alias :visit_Arel_Nodes_Offset :unary
-
1
alias :visit_Arel_Nodes_On :unary
-
1
alias :visit_Arel_Nodes_Top :unary
-
1
alias :visit_Arel_Nodes_UnqualifiedColumn :unary
-
1
alias :visit_Arel_Nodes_Preceding :unary
-
1
alias :visit_Arel_Nodes_Following :unary
-
1
alias :visit_Arel_Nodes_Rows :unary
-
1
alias :visit_Arel_Nodes_Range :unary
-
-
1
def window o
-
visit_edge o, "orders"
-
visit_edge o, "framing"
-
end
-
1
alias :visit_Arel_Nodes_Window :window
-
-
1
def named_window o
-
visit_edge o, "orders"
-
visit_edge o, "framing"
-
visit_edge o, "name"
-
end
-
1
alias :visit_Arel_Nodes_NamedWindow :named_window
-
-
1
def function o
-
visit_edge o, "expressions"
-
visit_edge o, "distinct"
-
visit_edge o, "alias"
-
end
-
1
alias :visit_Arel_Nodes_Exists :function
-
1
alias :visit_Arel_Nodes_Min :function
-
1
alias :visit_Arel_Nodes_Max :function
-
1
alias :visit_Arel_Nodes_Avg :function
-
1
alias :visit_Arel_Nodes_Sum :function
-
-
1
def extract o
-
visit_edge o, "expressions"
-
visit_edge o, "alias"
-
end
-
1
alias :visit_Arel_Nodes_Extract :extract
-
-
1
def visit_Arel_Nodes_NamedFunction o
-
visit_edge o, "name"
-
visit_edge o, "expressions"
-
visit_edge o, "distinct"
-
visit_edge o, "alias"
-
end
-
-
1
def visit_Arel_Nodes_InsertStatement o
-
visit_edge o, "relation"
-
visit_edge o, "columns"
-
visit_edge o, "values"
-
end
-
-
1
def visit_Arel_Nodes_SelectCore o
-
visit_edge o, "source"
-
visit_edge o, "projections"
-
visit_edge o, "wheres"
-
visit_edge o, "windows"
-
end
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
visit_edge o, "cores"
-
visit_edge o, "limit"
-
visit_edge o, "orders"
-
visit_edge o, "offset"
-
end
-
-
1
def visit_Arel_Nodes_UpdateStatement o
-
visit_edge o, "relation"
-
visit_edge o, "wheres"
-
visit_edge o, "values"
-
end
-
-
1
def visit_Arel_Table o
-
visit_edge o, "name"
-
end
-
-
1
def visit_Arel_Attribute o
-
visit_edge o, "relation"
-
visit_edge o, "name"
-
end
-
1
alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_String :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
-
-
1
def nary o
-
o.children.each_with_index do |x,i|
-
edge(i) { visit x }
-
end
-
end
-
1
alias :visit_Arel_Nodes_And :nary
-
-
1
def binary o
-
visit_edge o, "left"
-
visit_edge o, "right"
-
end
-
1
alias :visit_Arel_Nodes_As :binary
-
1
alias :visit_Arel_Nodes_Assignment :binary
-
1
alias :visit_Arel_Nodes_Between :binary
-
1
alias :visit_Arel_Nodes_DoesNotMatch :binary
-
1
alias :visit_Arel_Nodes_Equality :binary
-
1
alias :visit_Arel_Nodes_GreaterThan :binary
-
1
alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
-
1
alias :visit_Arel_Nodes_In :binary
-
1
alias :visit_Arel_Nodes_JoinSource :binary
-
1
alias :visit_Arel_Nodes_LessThan :binary
-
1
alias :visit_Arel_Nodes_LessThanOrEqual :binary
-
1
alias :visit_Arel_Nodes_Matches :binary
-
1
alias :visit_Arel_Nodes_NotEqual :binary
-
1
alias :visit_Arel_Nodes_NotIn :binary
-
1
alias :visit_Arel_Nodes_Or :binary
-
1
alias :visit_Arel_Nodes_Over :binary
-
-
1
def visit_String o
-
@node_stack.last.fields << o
-
end
-
1
alias :visit_Time :visit_String
-
1
alias :visit_Date :visit_String
-
1
alias :visit_DateTime :visit_String
-
1
alias :visit_NilClass :visit_String
-
1
alias :visit_TrueClass :visit_String
-
1
alias :visit_FalseClass :visit_String
-
1
alias :visit_Arel_SqlLiteral :visit_String
-
1
alias :visit_Fixnum :visit_String
-
1
alias :visit_BigDecimal :visit_String
-
1
alias :visit_Float :visit_String
-
1
alias :visit_Symbol :visit_String
-
1
alias :visit_Arel_Nodes_SqlLiteral :visit_String
-
-
1
def visit_Hash o
-
o.each_with_index do |pair, i|
-
edge("pair_#{i}") { visit pair }
-
end
-
end
-
-
1
def visit_Array o
-
o.each_with_index do |x,i|
-
edge(i) { visit x }
-
end
-
end
-
-
1
def visit_edge o, method
-
edge(method) { visit o.send(method) }
-
end
-
-
1
def visit o
-
if node = @seen[o.object_id]
-
@edge_stack.last.to = node
-
return
-
end
-
-
node = Node.new(o.class.name, o.object_id)
-
@seen[node.id] = node
-
@nodes << node
-
with_node node do
-
super
-
end
-
end
-
-
1
def edge name
-
edge = Edge.new(name, @node_stack.last)
-
@edge_stack.push edge
-
@edges << edge
-
yield
-
@edge_stack.pop
-
end
-
-
1
def with_node node
-
if edge = @edge_stack.last
-
edge.to = node
-
end
-
-
@node_stack.push node
-
yield
-
@node_stack.pop
-
end
-
-
1
def quote string
-
string.to_s.gsub('"', '\"')
-
end
-
-
1
def to_dot
-
"digraph \"ARel\" {\nnode [width=0.375,height=0.25,shape=record];\n" +
-
@nodes.map { |node|
-
label = "<f0>#{node.name}"
-
-
node.fields.each_with_index do |field, i|
-
label << "|<f#{i + 1}>#{quote field}"
-
end
-
-
"#{node.id} [label=\"#{label}\"];"
-
}.join("\n") + "\n" + @edges.map { |edge|
-
"#{edge.from.id} -> #{edge.to.id} [label=\"#{edge.name}\"];"
-
}.join("\n") + "\n}"
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class IBM_DB < Arel::Visitors::ToSql
-
1
private
-
-
1
def visit_Arel_Nodes_Limit o
-
"FETCH FIRST #{visit o.expr} ROWS ONLY"
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class Informix < Arel::Visitors::ToSql
-
1
private
-
1
def visit_Arel_Nodes_SelectStatement o
-
[
-
"SELECT",
-
(visit(o.offset) if o.offset),
-
(visit(o.limit) if o.limit),
-
o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
-
("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
-
(visit(o.lock) if o.lock),
-
].compact.join ' '
-
end
-
1
def visit_Arel_Nodes_SelectCore o
-
[
-
"#{o.projections.map { |x| visit x }.join ', '}",
-
("FROM #{visit(o.source)}" if o.source && !o.source.empty?),
-
("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
-
("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
-
(visit(o.having) if o.having),
-
].compact.join ' '
-
end
-
1
def visit_Arel_Nodes_Offset o
-
"SKIP #{visit o.expr}"
-
end
-
1
def visit_Arel_Nodes_Limit o
-
"LIMIT #{visit o.expr}"
-
end
-
end
-
end
-
end
-
-
1
module Arel
-
1
module Visitors
-
###
-
# This class produces SQL for JOIN clauses but omits the "single-source"
-
# part of the Join grammar:
-
#
-
# http://www.sqlite.org/syntaxdiagrams.html#join-source
-
#
-
# This visitor is used in SelectManager#join_sql and is for backwards
-
# compatibility with Arel V1.0
-
1
module JoinSql
-
1
private
-
-
1
def visit_Arel_Nodes_SelectCore o
-
o.source.right.map { |j| visit j }.join ' '
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class MSSQL < Arel::Visitors::ToSql
-
1
private
-
-
# `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate
-
# "select top 10 distinct first_name from users", which is invalid query! it should be
-
# "select distinct top 10 first_name from users"
-
1
def visit_Arel_Nodes_Top o
-
""
-
end
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
if !o.limit && !o.offset
-
return super o
-
end
-
-
select_order_by = "ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?
-
-
is_select_count = false
-
sql = o.cores.map { |x|
-
core_order_by = select_order_by || determine_order_by(x)
-
if select_count? x
-
x.projections = [row_num_literal(core_order_by)]
-
is_select_count = true
-
else
-
x.projections << row_num_literal(core_order_by)
-
end
-
-
visit_Arel_Nodes_SelectCore x
-
}.join
-
-
sql = "SELECT _t.* FROM (#{sql}) as _t WHERE #{get_offset_limit_clause(o)}"
-
# fixme count distinct wouldn't work with limit or offset
-
sql = "SELECT COUNT(1) as count_id FROM (#{sql}) AS subquery" if is_select_count
-
sql
-
end
-
-
1
def get_offset_limit_clause o
-
first_row = o.offset ? o.offset.expr.to_i + 1 : 1
-
last_row = o.limit ? o.limit.expr.to_i - 1 + first_row : nil
-
if last_row
-
" _row_num BETWEEN #{first_row} AND #{last_row}"
-
else
-
" _row_num >= #{first_row}"
-
end
-
end
-
-
1
def determine_order_by x
-
unless x.groups.empty?
-
"ORDER BY #{x.groups.map { |g| visit g }.join ', ' }"
-
else
-
"ORDER BY #{find_left_table_pk(x.froms)}"
-
end
-
end
-
-
1
def row_num_literal order_by
-
Nodes::SqlLiteral.new("ROW_NUMBER() OVER (#{order_by}) as _row_num")
-
end
-
-
1
def select_count? x
-
x.projections.length == 1 && Arel::Nodes::Count === x.projections.first
-
end
-
-
# fixme raise exception of there is no pk?
-
# fixme!! Table.primary_key will be depricated. What is the replacement??
-
1
def find_left_table_pk o
-
return visit o.primary_key if o.instance_of? Arel::Table
-
find_left_table_pk o.left if o.kind_of? Arel::Nodes::Join
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class MySQL < Arel::Visitors::ToSql
-
1
private
-
1
def visit_Arel_Nodes_Union o, suppress_parens = false
-
left_result = case o.left
-
when Arel::Nodes::Union
-
visit_Arel_Nodes_Union o.left, true
-
else
-
visit o.left
-
end
-
-
right_result = case o.right
-
when Arel::Nodes::Union
-
visit_Arel_Nodes_Union o.right, true
-
else
-
visit o.right
-
end
-
-
if suppress_parens
-
"#{left_result} UNION #{right_result}"
-
else
-
"( #{left_result} UNION #{right_result} )"
-
end
-
end
-
-
1
def visit_Arel_Nodes_Bin o
-
"BINARY #{visit o.expr}"
-
end
-
-
###
-
# :'(
-
# http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
-
1
def visit_Arel_Nodes_SelectStatement o
-
o.limit = Arel::Nodes::Limit.new(18446744073709551615) if o.offset && !o.limit
-
super
-
end
-
-
1
def visit_Arel_Nodes_SelectCore o
-
o.froms ||= Arel.sql('DUAL')
-
super
-
end
-
-
1
def visit_Arel_Nodes_UpdateStatement o
-
[
-
"UPDATE #{visit o.relation}",
-
("SET #{o.values.map { |value| visit value }.join ', '}" unless o.values.empty?),
-
("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?),
-
("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
-
(visit(o.limit) if o.limit),
-
].compact.join ' '
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class Oracle < Arel::Visitors::ToSql
-
1
private
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
o = order_hacks(o)
-
-
# if need to select first records without ORDER BY and GROUP BY and without DISTINCT
-
# then can use simple ROWNUM in WHERE clause
-
if o.limit && o.orders.empty? && !o.offset && o.cores.first.projections.first !~ /^DISTINCT /
-
o.cores.last.wheres.push Nodes::LessThanOrEqual.new(
-
Nodes::SqlLiteral.new('ROWNUM'), o.limit.expr
-
)
-
return super
-
end
-
-
if o.limit && o.offset
-
o = o.dup
-
limit = o.limit.expr.to_i
-
offset = o.offset
-
o.offset = nil
-
sql = super(o)
-
return <<-eosql
-
SELECT * FROM (
-
SELECT raw_sql_.*, rownum raw_rnum_
-
FROM (#{sql}) raw_sql_
-
)
-
WHERE raw_rnum_ between #{offset.expr.to_i + 1 } and #{offset.expr.to_i + limit}
-
eosql
-
end
-
-
if o.limit
-
o = o.dup
-
limit = o.limit.expr
-
return "SELECT * FROM (#{super(o)}) WHERE ROWNUM <= #{visit limit}"
-
end
-
-
if o.offset
-
o = o.dup
-
offset = o.offset
-
o.offset = nil
-
sql = super(o)
-
return <<-eosql
-
SELECT * FROM (
-
SELECT raw_sql_.*, rownum raw_rnum_
-
FROM (#{sql}) raw_sql_
-
)
-
WHERE #{visit offset}
-
eosql
-
end
-
-
super
-
end
-
-
1
def visit_Arel_Nodes_Limit o
-
end
-
-
1
def visit_Arel_Nodes_Offset o
-
"raw_rnum_ > #{visit o.expr}"
-
end
-
-
1
def visit_Arel_Nodes_Except o
-
"( #{visit o.left} MINUS #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_UpdateStatement o
-
# Oracle does not allow ORDER BY/LIMIT in UPDATEs.
-
if o.orders.any? && o.limit.nil?
-
# However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
-
# otherwise let the user deal with the error
-
o = o.dup
-
o.orders = []
-
end
-
-
super
-
end
-
-
###
-
# Hacks for the order clauses specific to Oracle
-
1
def order_hacks o
-
return o if o.orders.empty?
-
return o unless o.cores.any? do |core|
-
core.projections.any? do |projection|
-
/DISTINCT.*FIRST_VALUE/ === projection
-
end
-
end
-
# Previous version with join and split broke ORDER BY clause
-
# if it contained functions with several arguments (separated by ',').
-
#
-
# orders = o.orders.map { |x| visit x }.join(', ').split(',')
-
orders = o.orders.map do |x|
-
string = visit x
-
if string.include?(',')
-
split_order_string(string)
-
else
-
string
-
end
-
end.flatten
-
o.orders = []
-
orders.each_with_index do |order, i|
-
o.orders <<
-
Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i === order}")
-
end
-
o
-
end
-
-
# Split string by commas but count opening and closing brackets
-
# and ignore commas inside brackets.
-
1
def split_order_string(string)
-
array = []
-
i = 0
-
string.split(',').each do |part|
-
if array[i]
-
array[i] << ',' << part
-
else
-
# to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
-
array[i] = '' << part
-
end
-
i += 1 if array[i].count('(') == array[i].count(')')
-
end
-
array
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class OrderClauses < Arel::Visitors::ToSql
-
1
private
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
o.orders.map { |x| visit x }
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class PostgreSQL < Arel::Visitors::ToSql
-
1
private
-
-
1
def visit_Arel_Nodes_Matches o
-
"#{visit o.left} ILIKE #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_DoesNotMatch o
-
"#{visit o.left} NOT ILIKE #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_DistinctOn o
-
"DISTINCT ON ( #{visit o.expr} )"
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class SQLite < Arel::Visitors::ToSql
-
1
private
-
-
# Locks are not supported in SQLite
-
1
def visit_Arel_Nodes_Lock o
-
end
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit
-
super
-
end
-
end
-
end
-
end
-
1
require 'bigdecimal'
-
1
require 'date'
-
-
1
module Arel
-
1
module Visitors
-
1
class ToSql < Arel::Visitors::Visitor
-
##
-
# This is some roflscale crazy stuff. I'm roflscaling this because
-
# building SQL queries is a hotspot. I will explain the roflscale so that
-
# others will not rm this code.
-
#
-
# In YARV, string literals in a method body will get duped when the byte
-
# code is executed. Let's take a look:
-
#
-
# > puts RubyVM::InstructionSequence.new('def foo; "bar"; end').disasm
-
#
-
# == disasm: <RubyVM::InstructionSequence:foo@<compiled>>=====
-
# 0000 trace 8
-
# 0002 trace 1
-
# 0004 putstring "bar"
-
# 0006 trace 16
-
# 0008 leave
-
#
-
# The `putstring` bytecode will dup the string and push it on the stack.
-
# In many cases in our SQL visitor, that string is never mutated, so there
-
# is no need to dup the literal.
-
#
-
# If we change to a constant lookup, the string will not be duped, and we
-
# can reduce the objects in our system:
-
#
-
# > puts RubyVM::InstructionSequence.new('BAR = "bar"; def foo; BAR; end').disasm
-
#
-
# == disasm: <RubyVM::InstructionSequence:foo@<compiled>>========
-
# 0000 trace 8
-
# 0002 trace 1
-
# 0004 getinlinecache 11, <ic:0>
-
# 0007 getconstant :BAR
-
# 0009 setinlinecache <ic:0>
-
# 0011 trace 16
-
# 0013 leave
-
#
-
# `getconstant` should be a hash lookup, and no object is duped when the
-
# value of the constant is pushed on the stack. Hence the crazy
-
# constants below.
-
-
1
WHERE = ' WHERE ' # :nodoc:
-
1
SPACE = ' ' # :nodoc:
-
1
COMMA = ', ' # :nodoc:
-
1
GROUP_BY = ' GROUP BY ' # :nodoc:
-
1
ORDER_BY = ' ORDER BY ' # :nodoc:
-
1
WINDOW = ' WINDOW ' # :nodoc:
-
1
AND = ' AND ' # :nodoc:
-
-
1
DISTINCT = 'DISTINCT' # :nodoc:
-
-
1
attr_accessor :last_column
-
-
1
def initialize connection
-
1
@connection = connection
-
1
@schema_cache = connection.schema_cache
-
1
@quoted_tables = {}
-
1
@quoted_columns = {}
-
1
@last_column = nil
-
end
-
-
1
def accept object
-
self.last_column = nil
-
super
-
end
-
-
1
private
-
1
def visit_Arel_Nodes_DeleteStatement o
-
[
-
"DELETE FROM #{visit o.relation}",
-
("WHERE #{o.wheres.map { |x| visit x }.join AND}" unless o.wheres.empty?)
-
].compact.join ' '
-
end
-
-
# FIXME: we should probably have a 2-pass visitor for this
-
1
def build_subselect key, o
-
stmt = Nodes::SelectStatement.new
-
core = stmt.cores.first
-
core.froms = o.relation
-
core.wheres = o.wheres
-
core.projections = [key]
-
stmt.limit = o.limit
-
stmt.orders = o.orders
-
stmt
-
end
-
-
1
def visit_Arel_Nodes_UpdateStatement o
-
if o.orders.empty? && o.limit.nil?
-
wheres = o.wheres
-
else
-
key = o.key
-
unless key
-
warn(<<-eowarn) if $VERBOSE
-
(#{caller.first}) Using UpdateManager without setting UpdateManager#key is
-
deprecated and support will be removed in ARel 4.0.0. Please set the primary
-
key on UpdateManager using UpdateManager#key=
-
eowarn
-
key = o.relation.primary_key
-
end
-
-
wheres = [Nodes::In.new(key, [build_subselect(key, o)])]
-
end
-
-
[
-
"UPDATE #{visit o.relation}",
-
("SET #{o.values.map { |value| visit value }.join ', '}" unless o.values.empty?),
-
("WHERE #{wheres.map { |x| visit x }.join ' AND '}" unless wheres.empty?),
-
].compact.join ' '
-
end
-
-
1
def visit_Arel_Nodes_InsertStatement o
-
[
-
"INSERT INTO #{visit o.relation}",
-
-
("(#{o.columns.map { |x|
-
quote_column_name x.name
-
}.join ', '})" unless o.columns.empty?),
-
-
(visit o.values if o.values),
-
].compact.join ' '
-
end
-
-
1
def visit_Arel_Nodes_Exists o
-
"EXISTS (#{visit o.expressions})#{
-
o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_True o
-
"TRUE"
-
end
-
-
1
def visit_Arel_Nodes_False o
-
"FALSE"
-
end
-
-
1
def table_exists? name
-
@schema_cache.table_exists? name
-
end
-
-
1
def column_for attr
-
name = attr.name.to_s
-
table = attr.relation.table_name
-
-
return nil unless table_exists? table
-
-
column_cache[table][name]
-
end
-
-
1
def column_cache
-
@schema_cache.columns_hash
-
end
-
-
1
def visit_Arel_Nodes_Values o
-
"VALUES (#{o.expressions.zip(o.columns).map { |value, attr|
-
if Nodes::SqlLiteral === value
-
visit value
-
else
-
quote(value, attr && column_for(attr))
-
end
-
}.join ', '})"
-
end
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
str = ''
-
-
if o.with
-
str << visit(o.with)
-
str << SPACE
-
end
-
-
o.cores.each { |x| str << visit_Arel_Nodes_SelectCore(x) }
-
-
unless o.orders.empty?
-
str << SPACE
-
str << ORDER_BY
-
len = o.orders.length - 1
-
o.orders.each_with_index { |x, i|
-
str << visit(x)
-
str << COMMA unless len == i
-
}
-
end
-
-
str << " #{visit(o.limit)}" if o.limit
-
str << " #{visit(o.offset)}" if o.offset
-
str << " #{visit(o.lock)}" if o.lock
-
-
str.strip!
-
str
-
end
-
-
1
def visit_Arel_Nodes_SelectCore o
-
str = "SELECT"
-
-
str << " #{visit(o.top)}" if o.top
-
str << " #{visit(o.set_quantifier)}" if o.set_quantifier
-
-
unless o.projections.empty?
-
str << SPACE
-
len = o.projections.length - 1
-
o.projections.each_with_index do |x, i|
-
str << visit(x)
-
str << COMMA unless len == i
-
end
-
end
-
-
str << " FROM #{visit(o.source)}" if o.source && !o.source.empty?
-
-
unless o.wheres.empty?
-
str << WHERE
-
len = o.wheres.length - 1
-
o.wheres.each_with_index do |x, i|
-
str << visit(x)
-
str << AND unless len == i
-
end
-
end
-
-
unless o.groups.empty?
-
str << GROUP_BY
-
len = o.groups.length - 1
-
o.groups.each_with_index do |x, i|
-
str << visit(x)
-
str << COMMA unless len == i
-
end
-
end
-
-
str << " #{visit(o.having)}" if o.having
-
-
unless o.windows.empty?
-
str << WINDOW
-
len = o.windows.length - 1
-
o.windows.each_with_index do |x, i|
-
str << visit(x)
-
str << COMMA unless len == i
-
end
-
end
-
-
str
-
end
-
-
1
def visit_Arel_Nodes_Bin o
-
visit o.expr
-
end
-
-
1
def visit_Arel_Nodes_Distinct o
-
DISTINCT
-
end
-
-
1
def visit_Arel_Nodes_DistinctOn o
-
raise NotImplementedError, 'DISTINCT ON not implemented for this db'
-
end
-
-
1
def visit_Arel_Nodes_With o
-
"WITH #{o.children.map { |x| visit x }.join(', ')}"
-
end
-
-
1
def visit_Arel_Nodes_WithRecursive o
-
"WITH RECURSIVE #{o.children.map { |x| visit x }.join(', ')}"
-
end
-
-
1
def visit_Arel_Nodes_Union o
-
"( #{visit o.left} UNION #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_UnionAll o
-
"( #{visit o.left} UNION ALL #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_Intersect o
-
"( #{visit o.left} INTERSECT #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_Except o
-
"( #{visit o.left} EXCEPT #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_NamedWindow o
-
"#{quote_column_name o.name} AS #{visit_Arel_Nodes_Window o}"
-
end
-
-
1
def visit_Arel_Nodes_Window o
-
s = [
-
("ORDER BY #{o.orders.map { |x| visit(x) }.join(', ')}" unless o.orders.empty?),
-
(visit o.framing if o.framing)
-
].compact.join ' '
-
"(#{s})"
-
end
-
-
1
def visit_Arel_Nodes_Rows o
-
if o.expr
-
"ROWS #{visit o.expr}"
-
else
-
"ROWS"
-
end
-
end
-
-
1
def visit_Arel_Nodes_Range o
-
if o.expr
-
"RANGE #{visit o.expr}"
-
else
-
"RANGE"
-
end
-
end
-
-
1
def visit_Arel_Nodes_Preceding o
-
"#{o.expr ? visit(o.expr) : 'UNBOUNDED'} PRECEDING"
-
end
-
-
1
def visit_Arel_Nodes_Following o
-
"#{o.expr ? visit(o.expr) : 'UNBOUNDED'} FOLLOWING"
-
end
-
-
1
def visit_Arel_Nodes_CurrentRow o
-
"CURRENT ROW"
-
end
-
-
1
def visit_Arel_Nodes_Over o
-
case o.right
-
when nil
-
"#{visit o.left} OVER ()"
-
when Arel::Nodes::SqlLiteral
-
"#{visit o.left} OVER #{visit o.right}"
-
when String, Symbol
-
"#{visit o.left} OVER #{quote_column_name o.right.to_s}"
-
else
-
"#{visit o.left} OVER #{visit o.right}"
-
end
-
end
-
-
1
def visit_Arel_Nodes_Having o
-
"HAVING #{visit o.expr}"
-
end
-
-
1
def visit_Arel_Nodes_Offset o
-
"OFFSET #{visit o.expr}"
-
end
-
-
1
def visit_Arel_Nodes_Limit o
-
"LIMIT #{visit o.expr}"
-
end
-
-
# FIXME: this does nothing on most databases, but does on MSSQL
-
1
def visit_Arel_Nodes_Top o
-
""
-
end
-
-
1
def visit_Arel_Nodes_Lock o
-
visit o.expr
-
end
-
-
1
def visit_Arel_Nodes_Grouping o
-
"(#{visit o.expr})"
-
end
-
-
1
def visit_Arel_SelectManager o
-
"(#{o.to_sql.rstrip})"
-
end
-
-
1
def visit_Arel_Nodes_Ascending o
-
"#{visit o.expr} ASC"
-
end
-
-
1
def visit_Arel_Nodes_Descending o
-
"#{visit o.expr} DESC"
-
end
-
-
1
def visit_Arel_Nodes_Group o
-
visit o.expr
-
end
-
-
1
def visit_Arel_Nodes_NamedFunction o
-
"#{o.name}(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
-
visit x
-
}.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Extract o
-
"EXTRACT(#{o.field.to_s.upcase} FROM #{visit o.expr})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Count o
-
"COUNT(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
-
visit x
-
}.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Sum o
-
"SUM(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
-
visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Max o
-
"MAX(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
-
visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Min o
-
"MIN(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
-
visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Avg o
-
"AVG(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
-
visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_TableAlias o
-
"#{visit o.relation} #{quote_table_name o.name}"
-
end
-
-
1
def visit_Arel_Nodes_Between o
-
"#{visit o.left} BETWEEN #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_GreaterThanOrEqual o
-
"#{visit o.left} >= #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_GreaterThan o
-
"#{visit o.left} > #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_LessThanOrEqual o
-
"#{visit o.left} <= #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_LessThan o
-
"#{visit o.left} < #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_Matches o
-
"#{visit o.left} LIKE #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_DoesNotMatch o
-
"#{visit o.left} NOT LIKE #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_JoinSource o
-
[
-
(visit(o.left) if o.left),
-
o.right.map { |j| visit j }.join(' ')
-
].compact.join ' '
-
end
-
-
1
def visit_Arel_Nodes_StringJoin o
-
visit o.left
-
end
-
-
1
def visit_Arel_Nodes_OuterJoin o
-
"LEFT OUTER JOIN #{visit o.left} #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_InnerJoin o
-
s = "INNER JOIN #{visit o.left}"
-
if o.right
-
s << SPACE
-
s << visit(o.right)
-
end
-
s
-
end
-
-
1
def visit_Arel_Nodes_On o
-
"ON #{visit o.expr}"
-
end
-
-
1
def visit_Arel_Nodes_Not o
-
"NOT (#{visit o.expr})"
-
end
-
-
1
def visit_Arel_Table o
-
if o.table_alias
-
"#{quote_table_name o.name} #{quote_table_name o.table_alias}"
-
else
-
quote_table_name o.name
-
end
-
end
-
-
1
def visit_Arel_Nodes_In o
-
if Array === o.right && o.right.empty?
-
'1=0'
-
else
-
"#{visit o.left} IN (#{visit o.right})"
-
end
-
end
-
-
1
def visit_Arel_Nodes_NotIn o
-
if Array === o.right && o.right.empty?
-
'1=1'
-
else
-
"#{visit o.left} NOT IN (#{visit o.right})"
-
end
-
end
-
-
1
def visit_Arel_Nodes_And o
-
o.children.map { |x| visit x }.join ' AND '
-
end
-
-
1
def visit_Arel_Nodes_Or o
-
"#{visit o.left} OR #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_Assignment o
-
right = quote(o.right, column_for(o.left))
-
"#{visit o.left} = #{right}"
-
end
-
-
1
def visit_Arel_Nodes_Equality o
-
right = o.right
-
-
if right.nil?
-
"#{visit o.left} IS NULL"
-
else
-
"#{visit o.left} = #{visit right}"
-
end
-
end
-
-
1
def visit_Arel_Nodes_NotEqual o
-
right = o.right
-
-
if right.nil?
-
"#{visit o.left} IS NOT NULL"
-
else
-
"#{visit o.left} != #{visit right}"
-
end
-
end
-
-
1
def visit_Arel_Nodes_As o
-
"#{visit o.left} AS #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_UnqualifiedColumn o
-
"#{quote_column_name o.name}"
-
end
-
-
1
def visit_Arel_Attributes_Attribute o
-
self.last_column = column_for o
-
join_name = o.relation.table_alias || o.relation.name
-
"#{quote_table_name join_name}.#{quote_column_name o.name}"
-
end
-
1
alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_Decimal :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_String :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
-
-
1
def literal o; o end
-
-
1
alias :visit_Arel_Nodes_BindParam :literal
-
1
alias :visit_Arel_Nodes_SqlLiteral :literal
-
1
alias :visit_Arel_SqlLiteral :literal # This is deprecated
-
1
alias :visit_Bignum :literal
-
1
alias :visit_Fixnum :literal
-
-
1
def quoted o
-
quote(o, last_column)
-
end
-
-
1
alias :visit_ActiveSupport_Multibyte_Chars :quoted
-
1
alias :visit_ActiveSupport_StringInquirer :quoted
-
1
alias :visit_BigDecimal :quoted
-
1
alias :visit_Class :quoted
-
1
alias :visit_Date :quoted
-
1
alias :visit_DateTime :quoted
-
1
alias :visit_FalseClass :quoted
-
1
alias :visit_Float :quoted
-
1
alias :visit_Hash :quoted
-
1
alias :visit_NilClass :quoted
-
1
alias :visit_String :quoted
-
1
alias :visit_Symbol :quoted
-
1
alias :visit_Time :quoted
-
1
alias :visit_TrueClass :quoted
-
-
1
def visit_Arel_Nodes_InfixOperation o
-
"#{visit o.left} #{o.operator} #{visit o.right}"
-
end
-
-
1
alias :visit_Arel_Nodes_Addition :visit_Arel_Nodes_InfixOperation
-
1
alias :visit_Arel_Nodes_Subtraction :visit_Arel_Nodes_InfixOperation
-
1
alias :visit_Arel_Nodes_Multiplication :visit_Arel_Nodes_InfixOperation
-
1
alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation
-
-
1
def visit_Array o
-
o.map { |x| visit x }.join(', ')
-
end
-
-
1
def quote value, column = nil
-
@connection.quote value, column
-
end
-
-
1
def quote_table_name name
-
return name if Arel::Nodes::SqlLiteral === name
-
@quoted_tables[name] ||= @connection.quote_table_name(name)
-
end
-
-
1
def quote_column_name name
-
@quoted_columns[name] ||= Arel::Nodes::SqlLiteral === name ? name : @connection.quote_column_name(name)
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class Visitor
-
1
def accept object
-
visit object
-
end
-
-
1
private
-
-
1
DISPATCH = Hash.new do |hash, klass|
-
hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
-
end
-
-
1
def dispatch
-
DISPATCH
-
end
-
-
1
def visit object
-
send dispatch[object.class], object
-
rescue NoMethodError => e
-
raise e if respond_to?(dispatch[object.class], true)
-
superklass = object.class.ancestors.find { |klass|
-
respond_to?(dispatch[klass], true)
-
}
-
raise(TypeError, "Cannot visit #{object.class}") unless superklass
-
dispatch[object.class] = dispatch[superklass]
-
retry
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class WhereSql < Arel::Visitors::ToSql
-
1
def visit_Arel_Nodes_SelectCore o
-
"WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }"
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module WindowPredications
-
-
1
def over(expr = nil)
-
Nodes::Over.new(self, expr)
-
end
-
-
end
-
end
-
1
require 'journey/router'
-
1
require 'journey/gtg/builder'
-
1
require 'journey/gtg/simulator'
-
1
require 'journey/nfa/builder'
-
1
require 'journey/nfa/simulator'
-
1
module Journey
-
###
-
# The Formatter class is used for formatting URLs. For example, parameters
-
# passed to +url_for+ in rails will eventually call Formatter#generate
-
1
class Formatter
-
1
attr_reader :routes
-
-
1
def initialize routes
-
741
@routes = routes
-
741
@cache = nil
-
end
-
-
1
def generate type, name, options, recall = {}, parameterize = nil
-
4010
constraints = recall.merge options
-
4010
missing_keys = []
-
-
4010
match_route(name, constraints) do |route|
-
4010
parameterized_parts = extract_parameterized_parts route, options, recall, parameterize
-
4010
next if !name && route.requirements.empty? && route.parts.empty?
-
-
4009
missing_keys = missing_keys(route, parameterized_parts)
-
4009
next unless missing_keys.empty?
-
3999
params = options.dup.delete_if do |key, _|
-
10552
parameterized_parts.key?(key) || route.defaults.key?(key)
-
end
-
-
3999
return [route.format(parameterized_parts), params]
-
end
-
-
11
raise Router::RoutingError.new "missing required keys: #{missing_keys}"
-
end
-
-
1
def clear
-
746
@cache = nil
-
end
-
-
1
private
-
1
def extract_parameterized_parts route, options, recall, parameterize = nil
-
4010
constraints = recall.merge options
-
4010
data = constraints.dup
-
-
4010
keys_to_keep = route.parts.reverse.drop_while { |part|
-
7278
!options.key?(part) || (options[part] || recall[part]).nil?
-
} | route.required_parts
-
-
4010
(data.keys - keys_to_keep).each do |bad_key|
-
5417
data.delete bad_key
-
end
-
-
4010
parameterized_parts = data.dup
-
-
4010
if parameterize
-
4010
parameterized_parts.each do |k,v|
-
6018
parameterized_parts[k] = parameterize.call(k, v)
-
end
-
end
-
-
10028
parameterized_parts.keep_if { |_,v| v }
-
4010
parameterized_parts
-
end
-
-
1
def named_routes
-
4983
routes.named_routes
-
end
-
-
1
def match_route name, options
-
4010
if named_routes.key? name
-
973
yield named_routes[name]
-
else
-
#routes = possibles(@cache, options.to_a)
-
3037
routes = non_recursive(cache, options.to_a)
-
-
3037
hash = routes.group_by { |_, r|
-
3512
r.score options
-
}
-
-
3037
hash.keys.sort.reverse_each do |score|
-
3038
next if score < 0
-
-
6085
hash[score].sort_by { |i,_| i }.each do |_,route|
-
3037
yield route
-
end
-
end
-
end
-
end
-
-
1
def non_recursive cache, options
-
3037
routes = []
-
3037
stack = [cache]
-
-
3037
while stack.any?
-
4859
c = stack.shift
-
4859
routes.concat c[:___routes] if c.key? :___routes
-
-
4859
options.each do |pair|
-
13960
stack << c[pair] if c.key? pair
-
end
-
end
-
-
3037
routes
-
end
-
-
# returns an array populated with missing keys if any are present
-
1
def missing_keys route, parts
-
4009
missing_keys = []
-
4009
tests = route.path.requirements
-
4009
route.required_parts.each { |key|
-
3450
if tests.key? key
-
2224
missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
-
else
-
1226
missing_keys << key unless parts[key]
-
end
-
}
-
4009
missing_keys
-
end
-
-
1
def possibles cache, options, depth = 0
-
cache.fetch(:___routes) { [] } + options.find_all { |pair|
-
cache.key? pair
-
}.map { |pair|
-
possibles(cache[pair], options, depth + 1)
-
}.flatten(1)
-
end
-
-
# returns boolean, true if no missing keys are present
-
1
def verify_required_parts! route, parts
-
missing_keys(route, parts).empty?
-
end
-
-
1
def build_cache
-
339
root = { :___routes => [] }
-
339
routes.each_with_index do |route, i|
-
5950
leaf = route.required_defaults.inject(root) do |h, tuple|
-
11025
h[tuple] ||= {}
-
end
-
5950
(leaf[:___routes] ||= []) << [i, route]
-
end
-
339
root
-
end
-
-
1
def cache
-
3037
@cache ||= build_cache
-
end
-
end
-
end
-
1
require 'journey/gtg/transition_table'
-
-
1
module Journey
-
1
module GTG
-
1
class Builder
-
1
DUMMY = Nodes::Dummy.new # :nodoc:
-
-
1
attr_reader :root, :ast, :endpoints
-
-
1
def initialize root
-
8524
@root = root
-
8524
@ast = Nodes::Cat.new root, DUMMY
-
8524
@followpos = nil
-
end
-
-
1
def transition_table
-
307
dtrans = TransitionTable.new
-
307
marked = {}
-
5509
state_id = Hash.new { |h,k| h[k] = h.length }
-
-
307
start = firstpos(root)
-
307
dstates = [start]
-
307
until dstates.empty?
-
5223
s = dstates.shift
-
5223
next if marked[s]
-
4368
marked[s] = true # mark s
-
-
24537
s.group_by { |state| symbol(state) }.each do |sym, ps|
-
26839
u = ps.map { |l| followpos(l) }.flatten
-
6670
next if u.empty?
-
-
4916
if u.uniq == [DUMMY]
-
1309
from = state_id[s]
-
1309
to = state_id[Object.new]
-
1309
dtrans[from, to] = sym
-
-
1309
dtrans.add_accepting to
-
4374
ps.each { |state| dtrans.add_memo to, state.memo }
-
else
-
3607
dtrans[state_id[s], state_id[u]] = sym
-
-
3607
if u.include? DUMMY
-
1283
to = state_id[u]
-
-
5903
accepting = ps.find_all { |l| followpos(l).include? DUMMY }
-
-
1283
accepting.each { |accepting_state|
-
2166
dtrans.add_memo to, accepting_state.memo
-
}
-
-
1283
dtrans.add_accepting state_id[u]
-
end
-
end
-
-
4916
dstates << u
-
end
-
end
-
-
307
dtrans
-
end
-
-
1
def nullable? node
-
284176
case node
-
when Nodes::Group
-
12274
true
-
when Nodes::Star
-
30
true
-
when Nodes::Or
-
node.children.any? { |c| nullable?(c) }
-
when Nodes::Cat
-
30271
nullable?(node.left) && nullable?(node.right)
-
when Nodes::Terminal
-
241601
!node.left
-
when Nodes::Unary
-
nullable? node.left
-
else
-
raise ArgumentError, 'unknown nullable: %s' % node.class.name
-
end
-
end
-
-
1
def firstpos node
-
118072
case node
-
when Nodes::Star
-
38
firstpos(node.left)
-
when Nodes::Cat
-
23751
if nullable? node.left
-
18
firstpos(node.left) | firstpos(node.right)
-
else
-
23733
firstpos(node.left)
-
end
-
when Nodes::Or
-
3373
node.children.map { |c| firstpos(c) }.flatten.uniq
-
when Nodes::Unary
-
12216
firstpos(node.left)
-
when Nodes::Terminal
-
81760
nullable?(node) ? [] : [node]
-
else
-
raise ArgumentError, 'unknown firstpos: %s' % node.class.name
-
end
-
end
-
-
1
def lastpos node
-
160912
case node
-
when Nodes::Star
-
54
firstpos(node.left)
-
when Nodes::Or
-
3373
node.children.map { |c| lastpos(c) }.flatten.uniq
-
when Nodes::Cat
-
69754
if nullable? node.right
-
12214
lastpos(node.left) | lastpos(node.right)
-
else
-
57540
lastpos(node.right)
-
end
-
when Nodes::Terminal
-
78568
nullable?(node) ? [] : [node]
-
when Nodes::Unary
-
12229
lastpos(node.left)
-
else
-
raise ArgumentError, 'unknown lastpos: %s' % node.class.name
-
end
-
end
-
-
1
def followpos node
-
49903
followpos_table[node]
-
end
-
-
1
private
-
1
def followpos_table
-
49903
@followpos ||= build_followpos
-
end
-
-
1
def build_followpos
-
74187
table = Hash.new { |h,k| h[k] = [] }
-
7491
@ast.each do |n|
-
148619
case n
-
when Nodes::Cat
-
63630
lastpos(n.left).each do |i|
-
78603
table[i] += firstpos(n.right)
-
end
-
when Nodes::Star
-
19
lastpos(n).each do |i|
-
19
table[i] += firstpos(n)
-
end
-
end
-
end
-
7491
table
-
end
-
-
1
def symbol edge
-
20169
case edge
-
when Journey::Nodes::Symbol
-
3752
edge.regexp
-
else
-
16417
edge.left
-
end
-
end
-
end
-
end
-
end
-
1
require 'strscan'
-
-
1
module Journey
-
1
module GTG
-
1
class MatchData
-
1
attr_reader :memos
-
-
1
def initialize memos
-
2320
@memos = memos
-
end
-
end
-
-
1
class Simulator
-
1
attr_reader :tt
-
-
1
def initialize transition_table
-
307
@tt = transition_table
-
end
-
-
1
def simulate string
-
2539
input = StringScanner.new string
-
2539
state = [0]
-
2539
while sym = input.scan(%r([/.?]|[^/.?]+))
-
13919
state = tt.move(state, sym)
-
end
-
-
2539
acceptance_states = state.find_all { |s|
-
2656
tt.accepting? s
-
}
-
-
2539
return if acceptance_states.empty?
-
-
4888
memos = acceptance_states.map { |x| tt.memo x }.flatten.compact
-
-
2320
MatchData.new memos
-
end
-
-
1
alias :=~ :simulate
-
1
alias :match :simulate
-
end
-
end
-
end
-
-
1
require 'journey/nfa/dot'
-
-
1
module Journey
-
1
module GTG
-
1
class TransitionTable
-
1
include Journey::NFA::Dot
-
-
1
attr_reader :memos
-
-
1
def initialize
-
3176
@regexp_states = Hash.new { |h,k| h[k] = {} }
-
3208
@string_states = Hash.new { |h,k| h[k] = {} }
-
307
@accepting = {}
-
2895
@memos = Hash.new { |h,k| h[k] = [] }
-
end
-
-
1
def add_accepting state
-
2592
@accepting[state] = true
-
end
-
-
1
def accepting_states
-
@accepting.keys
-
end
-
-
1
def accepting? state
-
2656
@accepting[state]
-
end
-
-
1
def add_memo idx, memo
-
5231
@memos[idx] << memo
-
end
-
-
1
def memo idx
-
2568
@memos[idx]
-
end
-
-
1
def eclosure t
-
Array(t)
-
end
-
-
1
def move t, a
-
13919
move_string(t, a).concat move_regexp(t, a)
-
end
-
-
1
def to_json
-
require 'json'
-
-
simple_regexp = Hash.new { |h,k| h[k] = {} }
-
-
@regexp_states.each do |from, hash|
-
hash.each do |re, to|
-
simple_regexp[from][re.source] = to
-
end
-
end
-
-
JSON.dump({
-
:regexp_states => simple_regexp,
-
:string_states => @string_states,
-
:accepting => @accepting
-
})
-
end
-
-
1
def to_svg
-
svg = IO.popen("dot -Tsvg", 'w+') { |f|
-
f.write to_dot
-
f.close_write
-
f.readlines
-
}
-
3.times { svg.shift }
-
svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '')
-
end
-
-
1
def visualizer paths, title = 'FSM'
-
viz_dir = File.join File.dirname(__FILE__), '..', 'visualizer'
-
fsm_js = File.read File.join(viz_dir, 'fsm.js')
-
fsm_css = File.read File.join(viz_dir, 'fsm.css')
-
erb = File.read File.join(viz_dir, 'index.html.erb')
-
states = "function tt() { return #{to_json}; }"
-
-
fun_routes = paths.shuffle.first(3).map do |ast|
-
ast.map { |n|
-
case n
-
when Nodes::Symbol
-
case n.left
-
when ':id' then rand(100).to_s
-
when ':format' then %w{ xml json }.shuffle.first
-
else
-
'omg'
-
end
-
when Nodes::Terminal then n.symbol
-
else
-
nil
-
end
-
}.compact.join
-
end
-
-
stylesheets = [fsm_css]
-
svg = to_svg
-
javascripts = [states, fsm_js]
-
-
# Annoying hack for 1.9 warnings
-
fun_routes = fun_routes
-
stylesheets = stylesheets
-
svg = svg
-
javascripts = javascripts
-
-
require 'erb'
-
template = ERB.new erb
-
template.result(binding)
-
end
-
-
1
def []= from, to, sym
-
4916
case sym
-
when String
-
3281
@string_states[from][sym] = to
-
when Regexp
-
1635
@regexp_states[from][sym] = to
-
else
-
raise ArgumentError, 'unknown symbol: %s' % sym.class
-
end
-
end
-
-
1
def states
-
ss = @string_states.keys + @string_states.values.map(&:values).flatten
-
rs = @regexp_states.keys + @regexp_states.values.map(&:values).flatten
-
(ss + rs).uniq
-
end
-
-
1
def transitions
-
@string_states.map { |from, hash|
-
hash.map { |s, to| [from, s, to] }
-
}.flatten(1) + @regexp_states.map { |from, hash|
-
hash.map { |s, to| [from, s, to] }
-
}.flatten(1)
-
end
-
-
1
private
-
1
def move_regexp t, a
-
13919
return [] if t.empty?
-
-
13505
t.map { |s|
-
17475
@regexp_states[s].map { |re,v| re === a ? v : nil }
-
}.flatten.compact.uniq
-
end
-
-
1
def move_string t, a
-
13919
return [] if t.empty?
-
-
27780
t.map { |s| @string_states[s][a] }.compact
-
end
-
end
-
end
-
end
-
1
require 'journey/nfa/transition_table'
-
1
require 'journey/gtg/transition_table'
-
-
1
module Journey
-
1
module NFA
-
1
class Visitor < Visitors::Visitor
-
1
def initialize tt
-
@tt = tt
-
@i = -1
-
end
-
-
1
def visit_CAT node
-
left = visit node.left
-
right = visit node.right
-
-
@tt.merge left.last, right.first
-
-
[left.first, right.last]
-
end
-
-
1
def visit_GROUP node
-
from = @i += 1
-
left = visit node.left
-
to = @i += 1
-
-
@tt.accepting = to
-
-
@tt[from, left.first] = nil
-
@tt[left.last, to] = nil
-
@tt[from, to] = nil
-
-
[from, to]
-
end
-
-
1
def visit_OR node
-
from = @i += 1
-
children = node.children.map { |c| visit c }
-
to = @i += 1
-
-
children.each do |child|
-
@tt[from, child.first] = nil
-
@tt[child.last, to] = nil
-
end
-
-
@tt.accepting = to
-
-
[from, to]
-
end
-
-
1
def terminal node
-
from_i = @i += 1 # new state
-
to_i = @i += 1 # new state
-
-
@tt[from_i, to_i] = node
-
@tt.accepting = to_i
-
@tt.add_memo to_i, node.memo
-
-
[from_i, to_i]
-
end
-
end
-
-
1
class Builder
-
1
def initialize ast
-
@ast = ast
-
end
-
-
1
def transition_table
-
tt = TransitionTable.new
-
Visitor.new(tt).accept @ast
-
tt
-
end
-
end
-
end
-
end
-
# encoding: utf-8
-
-
1
module Journey
-
1
module NFA
-
1
module Dot
-
1
def to_dot
-
edges = transitions.map { |from, sym, to|
-
" #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
-
}
-
-
#memo_nodes = memos.values.flatten.map { |n|
-
# label = n
-
# if Journey::Route === n
-
# label = "#{n.verb.source} #{n.path.spec}"
-
# end
-
# " #{n.object_id} [label=\"#{label}\", shape=box];"
-
#}
-
#memo_edges = memos.map { |k, memos|
-
# (memos || []).map { |v| " #{k} -> #{v.object_id};" }
-
#}.flatten.uniq
-
-
<<-eodot
-
digraph nfa {
-
rankdir=LR;
-
node [shape = doublecircle];
-
#{accepting_states.join ' '};
-
node [shape = circle];
-
#{edges.join "\n"}
-
}
-
eodot
-
end
-
end
-
end
-
end
-
1
require 'strscan'
-
-
1
module Journey
-
1
module NFA
-
1
class MatchData
-
1
attr_reader :memos
-
-
1
def initialize memos
-
@memos = memos
-
end
-
end
-
-
1
class Simulator
-
1
attr_reader :tt
-
-
1
def initialize transition_table
-
@tt = transition_table
-
end
-
-
1
def simulate string
-
input = StringScanner.new string
-
state = tt.eclosure 0
-
until input.eos?
-
sym = input.scan(%r([/.?]|[^/.?]+))
-
-
# FIXME: tt.eclosure is not needed for the GTG
-
state = tt.eclosure tt.move(state, sym)
-
end
-
-
acceptance_states = state.find_all { |s|
-
tt.accepting? tt.eclosure(s).sort.last
-
}
-
-
return if acceptance_states.empty?
-
-
memos = acceptance_states.map { |x| tt.memo x }.flatten.compact
-
-
MatchData.new memos
-
end
-
-
1
alias :=~ :simulate
-
1
alias :match :simulate
-
end
-
end
-
end
-
1
require 'journey/nfa/dot'
-
-
1
module Journey
-
1
module NFA
-
1
class TransitionTable
-
1
include Journey::NFA::Dot
-
-
1
attr_accessor :accepting
-
1
attr_reader :memos
-
-
1
def initialize
-
@table = Hash.new { |h,f| h[f] = {} }
-
@memos = {}
-
@accepting = nil
-
@inverted = nil
-
end
-
-
1
def accepting? state
-
accepting == state
-
end
-
-
1
def accepting_states
-
[accepting]
-
end
-
-
1
def add_memo idx, memo
-
@memos[idx] = memo
-
end
-
-
1
def memo idx
-
@memos[idx]
-
end
-
-
1
def []= i, f, s
-
@table[f][i] = s
-
end
-
-
1
def merge left, right
-
@memos[right] = @memos.delete left
-
@table[right] = @table.delete(left)
-
end
-
-
1
def states
-
(@table.keys + @table.values.map(&:keys).flatten).uniq
-
end
-
-
###
-
# Returns a generalized transition graph with reduced states. The states
-
# are reduced like a DFA, but the table must be simulated like an NFA.
-
#
-
# Edges of the GTG are regular expressions
-
1
def generalized_table
-
gt = GTG::TransitionTable.new
-
marked = {}
-
state_id = Hash.new { |h,k| h[k] = h.length }
-
alphabet = self.alphabet
-
-
stack = [eclosure(0)]
-
-
until stack.empty?
-
state = stack.pop
-
next if marked[state] || state.empty?
-
-
marked[state] = true
-
-
alphabet.each do |alpha|
-
next_state = eclosure(following_states(state, alpha))
-
next if next_state.empty?
-
-
gt[state_id[state], state_id[next_state]] = alpha
-
stack << next_state
-
end
-
end
-
-
final_groups = state_id.keys.find_all { |s|
-
s.sort.last == accepting
-
}
-
-
final_groups.each do |states|
-
id = state_id[states]
-
-
gt.add_accepting id
-
save = states.find { |s|
-
@memos.key?(s) && eclosure(s).sort.last == accepting
-
}
-
-
gt.add_memo id, memo(save)
-
end
-
-
gt
-
end
-
-
###
-
# Returns set of NFA states to which there is a transition on ast symbol
-
# +a+ from some state +s+ in +t+.
-
1
def following_states t, a
-
Array(t).map { |s| inverted[s][a] }.flatten.uniq
-
end
-
-
###
-
# Returns set of NFA states to which there is a transition on ast symbol
-
# +a+ from some state +s+ in +t+.
-
1
def move t, a
-
Array(t).map { |s|
-
inverted[s].keys.compact.find_all { |sym|
-
sym === a
-
}.map { |sym| inverted[s][sym] }
-
}.flatten.uniq
-
end
-
-
1
def alphabet
-
inverted.values.map(&:keys).flatten.compact.uniq.sort_by { |x| x.to_s }
-
end
-
-
###
-
# Returns a set of NFA states reachable from some NFA state +s+ in set
-
# +t+ on nil-transitions alone.
-
1
def eclosure t
-
stack = Array(t)
-
seen = {}
-
children = []
-
-
until stack.empty?
-
s = stack.pop
-
next if seen[s]
-
-
seen[s] = true
-
children << s
-
-
stack.concat inverted[s][nil]
-
end
-
-
children.uniq
-
end
-
-
1
def transitions
-
@table.map { |to, hash|
-
hash.map { |from, sym| [from, sym, to] }
-
}.flatten(1)
-
end
-
-
1
private
-
1
def inverted
-
return @inverted if @inverted
-
-
@inverted = Hash.new { |h,from|
-
h[from] = Hash.new { |j,s| j[s] = [] }
-
}
-
-
@table.each { |to, hash|
-
hash.each { |from, sym|
-
if sym
-
sym = Nodes::Symbol === sym ? sym.regexp : sym.left
-
end
-
-
@inverted[from][sym] << to
-
}
-
}
-
-
@inverted
-
end
-
end
-
end
-
end
-
#
-
# DO NOT MODIFY!!!!
-
# This file is automatically generated by Racc 1.4.8
-
# from Racc grammer file "".
-
#
-
-
1
require 'racc/parser.rb'
-
-
-
1
require 'journey/parser_extras'
-
1
module Journey
-
1
class Parser < Racc::Parser
-
##### State transition tables begin ###
-
-
1
racc_action_table = [
-
17, 21, 13, 15, 14, 7, nil, 16, 8, 19,
-
13, 15, 14, 7, 23, 16, 8, 19, 13, 15,
-
14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
-
16, 8, 13, 15, 14, 7, nil, 16, 8 ]
-
-
1
racc_action_check = [
-
1, 17, 1, 1, 1, 1, nil, 1, 1, 1,
-
20, 20, 20, 20, 20, 20, 20, 20, 7, 7,
-
7, 7, nil, 7, 7, 19, 19, 19, 19, nil,
-
19, 19, 0, 0, 0, 0, nil, 0, 0 ]
-
-
1
racc_action_pointer = [
-
30, 0, nil, nil, nil, nil, nil, 16, nil, nil,
-
nil, nil, nil, nil, nil, nil, nil, 1, nil, 23,
-
8, nil, nil, nil ]
-
-
1
racc_action_default = [
-
-18, -18, -2, -3, -4, -5, -6, -18, -9, -10,
-
-11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
-
-18, 24, -8, -7 ]
-
-
1
racc_goto_table = [
-
18, 1, nil, nil, nil, nil, nil, nil, 20, nil,
-
nil, nil, nil, nil, nil, nil, nil, nil, 22, 18 ]
-
-
1
racc_goto_check = [
-
2, 1, nil, nil, nil, nil, nil, nil, 1, nil,
-
nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
-
-
1
racc_goto_pointer = [
-
nil, 1, -1, nil, nil, nil, nil, nil, nil, nil,
-
nil ]
-
-
1
racc_goto_default = [
-
nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
-
12 ]
-
-
1
racc_reduce_table = [
-
0, 0, :racc_error,
-
2, 11, :_reduce_1,
-
1, 11, :_reduce_2,
-
1, 11, :_reduce_none,
-
1, 12, :_reduce_none,
-
1, 12, :_reduce_none,
-
1, 12, :_reduce_none,
-
3, 15, :_reduce_7,
-
3, 13, :_reduce_8,
-
1, 16, :_reduce_9,
-
1, 14, :_reduce_none,
-
1, 14, :_reduce_none,
-
1, 14, :_reduce_none,
-
1, 14, :_reduce_none,
-
1, 19, :_reduce_14,
-
1, 17, :_reduce_15,
-
1, 18, :_reduce_16,
-
1, 20, :_reduce_17 ]
-
-
1
racc_reduce_n = 18
-
-
1
racc_shift_n = 24
-
-
1
racc_token_table = {
-
false => 0,
-
:error => 1,
-
:SLASH => 2,
-
:LITERAL => 3,
-
:SYMBOL => 4,
-
:LPAREN => 5,
-
:RPAREN => 6,
-
:DOT => 7,
-
:STAR => 8,
-
:OR => 9 }
-
-
1
racc_nt_base = 10
-
-
1
racc_use_result_var = true
-
-
1
Racc_arg = [
-
racc_action_table,
-
racc_action_check,
-
racc_action_default,
-
racc_action_pointer,
-
racc_goto_table,
-
racc_goto_check,
-
racc_goto_default,
-
racc_goto_pointer,
-
racc_nt_base,
-
racc_reduce_table,
-
racc_token_table,
-
racc_shift_n,
-
racc_reduce_n,
-
racc_use_result_var ]
-
-
1
Racc_token_to_s_table = [
-
"$end",
-
"error",
-
"SLASH",
-
"LITERAL",
-
"SYMBOL",
-
"LPAREN",
-
"RPAREN",
-
"DOT",
-
"STAR",
-
"OR",
-
"$start",
-
"expressions",
-
"expression",
-
"or",
-
"terminal",
-
"group",
-
"star",
-
"symbol",
-
"literal",
-
"slash",
-
"dot" ]
-
-
1
Racc_debug_parser = false
-
-
##### State transition tables end #####
-
-
# reduce 0 omitted
-
-
1
def _reduce_1(val, _values, result)
-
52321
result = Cat.new(val.first, val.last)
-
52321
result
-
end
-
-
1
def _reduce_2(val, _values, result)
-
20617
result = val.first
-
20617
result
-
end
-
-
# reduce 3 omitted
-
-
# reduce 4 omitted
-
-
# reduce 5 omitted
-
-
# reduce 6 omitted
-
-
1
def _reduce_7(val, _values, result)
-
11069
result = Group.new(val[1])
-
11069
result
-
end
-
-
1
def _reduce_8(val, _values, result)
-
result = Or.new([val.first, val.last])
-
result
-
end
-
-
1
def _reduce_9(val, _values, result)
-
41
result = Star.new(Symbol.new(val.last))
-
41
result
-
end
-
-
# reduce 10 omitted
-
-
# reduce 11 omitted
-
-
# reduce 12 omitted
-
-
# reduce 13 omitted
-
-
1
def _reduce_14(val, _values, result)
-
22949
result = Slash.new('/')
-
22949
result
-
end
-
-
1
def _reduce_15(val, _values, result)
-
17532
result = Symbol.new(val.first)
-
17532
result
-
end
-
-
1
def _reduce_16(val, _values, result)
-
12847
result = Literal.new(val.first)
-
12847
result
-
end
-
-
1
def _reduce_17(val, _values, result)
-
8500
result = Dot.new(val.first)
-
8500
result
-
end
-
-
1
def _reduce_none(val, _values, result)
-
val[0]
-
end
-
-
end # class Parser
-
end # module Journey
-
1
require 'journey/scanner'
-
1
require 'journey/nodes/node'
-
-
1
module Journey
-
1
class Parser < Racc::Parser
-
1
include Journey::Nodes
-
-
1
def initialize
-
9548
@scanner = Scanner.new
-
end
-
-
1
def parse string
-
9548
@scanner.scan_setup string
-
9548
do_parse
-
end
-
-
1
def next_token
-
93555
@scanner.next_token
-
end
-
end
-
end
-
1
module Journey
-
1
class Route
-
1
attr_reader :app, :path, :verb, :defaults, :ip, :name
-
-
1
attr_reader :constraints
-
1
alias :conditions :constraints
-
-
1
attr_accessor :precedence
-
-
##
-
# +path+ is a path constraint.
-
# +constraints+ is a hash of constraints to be applied to this route.
-
1
def initialize name, app, path, constraints, defaults = {}
-
10933
constraints = constraints.dup
-
10933
@name = name
-
10933
@app = app
-
10933
@path = path
-
10933
@verb = constraints[:request_method] || //
-
10933
@ip = constraints.delete(:ip) || //
-
-
10933
@constraints = constraints
-
21977
@constraints.keep_if { |_,v| Regexp === v || String === v }
-
10933
@defaults = defaults
-
10933
@required_defaults = nil
-
10933
@required_parts = nil
-
10933
@parts = nil
-
10933
@decorated_ast = nil
-
10933
@precedence = 0
-
end
-
-
1
def ast
-
12886
return @decorated_ast if @decorated_ast
-
-
5513
@decorated_ast = path.ast
-
37488
@decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
-
5513
@decorated_ast
-
end
-
-
1
def requirements # :nodoc:
-
# needed for rails `rake routes`
-
9676
path.requirements.merge(@defaults).delete_if { |_,v|
-
17974
/.+?/ == v
-
}
-
end
-
-
1
def segments
-
6475
@path.names
-
end
-
-
1
def required_keys
-
path.required_names.map { |x| x.to_sym } + required_defaults.keys
-
end
-
-
1
def score constraints
-
3512
required_keys = path.required_names
-
13177
supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
-
-
3512
return -1 unless (required_keys - supplied_keys).empty?
-
-
3282
score = (supplied_keys & path.names).length
-
3282
score + (required_defaults.length * 2)
-
end
-
-
1
def parts
-
31324
@parts ||= segments.map { |n| n.to_sym }
-
end
-
1
alias :segment_keys :parts
-
-
1
def format path_options
-
3999
path_options.delete_if do |key, value|
-
6001
value.to_s == defaults[key].to_s && !required_parts.include?(key)
-
end
-
-
3999
Visitors::Formatter.new(path_options).accept(path.spec)
-
end
-
-
1
def optional_parts
-
path.optional_names.map { |n| n.to_sym }
-
end
-
-
1
def required_parts
-
23190
@required_parts ||= path.required_names.map { |n| n.to_sym }
-
end
-
-
1
def required_defaults
-
@required_defaults ||= begin
-
5950
matches = parts
-
17488
@defaults.dup.delete_if { |k,_| matches.include? k }
-
9232
end
-
end
-
end
-
end
-
1
require 'journey/router/utils'
-
1
require 'journey/router/strexp'
-
1
require 'journey/routes'
-
1
require 'journey/formatter'
-
-
1
before = $-w
-
1
$-w = false
-
1
require 'journey/parser'
-
1
$-w = before
-
-
1
require 'journey/route'
-
1
require 'journey/path/pattern'
-
-
1
module Journey
-
1
class Router
-
1
class RoutingError < ::StandardError
-
end
-
-
1
VERSION = '2.0.0'
-
-
1
class NullReq # :nodoc:
-
1
attr_reader :env
-
1
def initialize env
-
@env = env
-
end
-
-
1
def request_method
-
env['REQUEST_METHOD']
-
end
-
-
1
def path_info
-
env['PATH_INFO']
-
end
-
-
1
def ip
-
env['REMOTE_ADDR']
-
end
-
-
1
def [](k); env[k]; end
-
end
-
-
1
attr_reader :request_class, :formatter
-
1
attr_accessor :routes
-
-
1
def initialize routes, options
-
741
@options = options
-
741
@params_key = options[:parameters_key]
-
741
@request_class = options[:request_class] || NullReq
-
741
@routes = routes
-
end
-
-
1
def call env
-
745
env['PATH_INFO'] = Utils.normalize_path env['PATH_INFO']
-
-
745
find_routes(env).each do |match, parameters, route|
-
706
script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
-
'PATH_INFO',
-
@params_key)
-
-
706
unless route.path.anchored
-
12
env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
-
12
env['PATH_INFO'] = match.post_match
-
end
-
-
706
env[@params_key] = (set_params || {}).merge parameters
-
-
706
status, headers, body = route.app.call(env)
-
-
693
if 'pass' == headers['X-Cascade']
-
8
env['SCRIPT_NAME'] = script_name
-
8
env['PATH_INFO'] = path_info
-
8
env[@params_key] = set_params
-
8
next
-
end
-
-
685
return [status, headers, body]
-
end
-
-
47
return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
-
end
-
-
1
def recognize req
-
1984
find_routes(req.env).each do |match, parameters, route|
-
1816
unless route.path.anchored
-
req.env['SCRIPT_NAME'] = match.to_s
-
req.env['PATH_INFO'] = match.post_match.sub(/^([^\/])/, '/\1')
-
end
-
-
1816
yield(route, nil, parameters)
-
end
-
end
-
-
1
def visualizer
-
tt = GTG::Builder.new(ast).transition_table
-
groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
-
asts = groups.values.map { |v| v.first }
-
tt.visualizer asts
-
end
-
-
1
private
-
-
1
def partitioned_routes
-
2729
routes.partitioned_routes
-
end
-
-
1
def ast
-
2729
routes.ast
-
end
-
-
1
def simulator
-
2539
routes.simulator
-
end
-
-
1
def custom_routes
-
2729
partitioned_routes.last
-
end
-
-
1
def filter_routes path
-
2729
return [] unless ast
-
2539
data = simulator.match(path)
-
2539
data ? data.memos : []
-
end
-
-
1
def find_routes env
-
2729
req = request_class.new env
-
-
2729
routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
-
38659
r.path.match(req.path_info)
-
}
-
2729
routes.concat get_routes_as_head(routes)
-
-
2729
routes.sort_by!(&:precedence).select! { |r|
-
10074
r.constraints.all? { |k,v| v === req.send(k) } &&
-
10145
r.verb === req.request_method
-
}
-
5592
routes.reject! { |r| req.ip && !(r.ip === req.ip) }
-
-
2728
routes.map! { |r|
-
2862
match_data = r.path.match(req.path_info)
-
8288
match_names = match_data.names.map { |n| n.to_sym }
-
8288
match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
-
8288
info = Hash[match_names.zip(match_values).find_all { |_,y| y }]
-
-
2862
[match_data, r.defaults.merge(info), r]
-
}
-
end
-
-
1
def get_routes_as_head(routes)
-
2729
precedence = (routes.map(&:precedence).max || 0) + 1
-
2729
routes = routes.select { |r|
-
7431
r.verb === "GET" && !(r.verb === "HEAD")
-
}.map! { |r|
-
2716
Route.new(r.name,
-
r.app,
-
r.path,
-
r.conditions.merge(:request_method => "HEAD"),
-
r.defaults).tap do |route|
-
2716
route.precedence = r.precedence + precedence
-
end
-
}
-
2729
routes.flatten!
-
2729
routes
-
end
-
end
-
end
-
1
module Journey
-
1
class Router
-
1
class Strexp
-
1
class << self
-
1
alias :compile :new
-
end
-
-
1
attr_reader :path, :requirements, :separators, :anchor
-
-
1
def initialize path, requirements, separators, anchor = true
-
9548
@path = path
-
9548
@requirements = requirements
-
9548
@separators = separators
-
9548
@anchor = anchor
-
end
-
-
1
def names
-
@path.scan(/:\w+/).map { |s| s.tr(':', '') }
-
end
-
end
-
end
-
end
-
1
require 'uri'
-
-
1
module Journey
-
1
class Router
-
1
class Utils
-
# Normalizes URI path.
-
#
-
# Strips off trailing slash and ensures there is a leading slash.
-
#
-
# normalize_path("/foo") # => "/foo"
-
# normalize_path("/foo/") # => "/foo"
-
# normalize_path("foo") # => "/foo"
-
# normalize_path("") # => "/"
-
1
def self.normalize_path(path)
-
17655
path = "/#{path}"
-
17655
path.squeeze!('/')
-
17655
path.sub!(%r{/+\Z}, '')
-
17655
path = '/' if path == ''
-
17655
path
-
end
-
-
# URI path and fragment escaping
-
# http://tools.ietf.org/html/rfc3986
-
1
module UriEscape
-
# Symbol captures can generate multiple path segments, so include /.
-
1
reserved_segment = '/'
-
1
reserved_fragment = '/?'
-
1
reserved_pchar = ':@&=+$,;%'
-
-
1
safe_pchar = "#{URI::REGEXP::PATTERN::UNRESERVED}#{reserved_pchar}"
-
1
safe_segment = "#{safe_pchar}#{reserved_segment}"
-
1
safe_fragment = "#{safe_pchar}#{reserved_fragment}"
-
1
if RUBY_VERSION >= '1.9'
-
1
UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false).freeze
-
1
UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
-
else
-
UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false, 'N').freeze
-
UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false, 'N').freeze
-
end
-
end
-
-
1
Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
-
-
1
def self.escape_path(path)
-
5991
Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
-
end
-
-
1
def self.escape_fragment(fragment)
-
50
Parser.escape(fragment.to_s, UriEscape::UNSAFE_FRAGMENT)
-
end
-
-
1
def self.unescape_uri(uri)
-
3432
Parser.unescape(uri)
-
end
-
end
-
end
-
end
-
1
module Journey
-
###
-
# The Routing table. Contains all routes for a system. Routes can be
-
# added to the table by calling Routes#add_route
-
1
class Routes
-
1
include Enumerable
-
-
1
attr_reader :routes, :named_routes
-
-
1
def initialize
-
741
@routes = []
-
741
@named_routes = {}
-
741
@ast = nil
-
741
@partitioned_routes = nil
-
741
@simulator = nil
-
end
-
-
1
def length
-
7
@routes.length
-
end
-
1
alias :size :length
-
-
1
def last
-
@routes.last
-
end
-
-
1
def each(&block)
-
5592
routes.each(&block)
-
end
-
-
1
def clear
-
746
routes.clear
-
end
-
-
1
def partitioned_routes
-
@partitioned_routes ||= routes.partition { |r|
-
7260
r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
-
3533
}
-
end
-
-
1
def ast
-
3036
return @ast if @ast
-
497
return if partitioned_routes.first.empty?
-
-
3373
asts = partitioned_routes.first.map { |r| r.ast }
-
307
@ast = Nodes::Or.new(asts)
-
end
-
-
1
def simulator
-
2539
return @simulator if @simulator
-
-
307
gtg = GTG::Builder.new(ast).transition_table
-
307
@simulator = GTG::Simulator.new gtg
-
end
-
-
###
-
# Add a route to the routing table.
-
1
def add_route app, path, conditions, defaults, name = nil
-
8217
route = Route.new(name, app, path, conditions, defaults)
-
-
8217
route.precedence = routes.length
-
8217
routes << route
-
8217
named_routes[name] = route if name && !named_routes[name]
-
8217
clear_cache!
-
8217
route
-
end
-
-
1
private
-
1
def clear_cache!
-
8217
@ast = nil
-
8217
@partitioned_routes = nil
-
8217
@simulator = nil
-
end
-
end
-
end
-
1
require 'strscan'
-
-
1
module Journey
-
1
class Scanner
-
1
def initialize
-
9548
@ss = nil
-
end
-
-
1
def scan_setup str
-
9548
@ss = StringScanner.new str
-
end
-
-
1
def eos?
-
@ss.eos?
-
end
-
-
1
def pos
-
@ss.pos
-
end
-
-
1
def pre_match
-
@ss.pre_match
-
end
-
-
1
def next_token
-
93555
return if @ss.eos?
-
-
84007
until token = scan || @ss.eos?; end
-
84007
token
-
end
-
-
1
private
-
1
def scan
-
case
-
# /
-
when text = @ss.scan(/\//)
-
22949
[:SLASH, text]
-
when text = @ss.scan(/\*\w+/)
-
41
[:STAR, text]
-
when text = @ss.scan(/\(/)
-
11069
[:LPAREN, text]
-
when text = @ss.scan(/\)/)
-
11069
[:RPAREN, text]
-
when text = @ss.scan(/\|/)
-
[:OR, text]
-
when text = @ss.scan(/\./)
-
8500
[:DOT, text]
-
when text = @ss.scan(/:\w+/)
-
17532
[:SYMBOL, text]
-
when text = @ss.scan(/[\w%\-~]+/)
-
12847
[:LITERAL, text]
-
# any char
-
when text = @ss.scan(/./)
-
[:LITERAL, text]
-
84007
end
-
end
-
end
-
end
-
1
require 'mocha/class_method'
-
-
1
module Mocha
-
-
1
class AnyInstanceMethod < ClassMethod
-
-
1
def mock
-
42
stubbee.any_instance.mocha
-
end
-
-
1
def reset_mocha
-
19
stubbee.any_instance.reset_mocha
-
end
-
-
1
def hide_original_method
-
21
if method_exists?(method)
-
15
begin
-
15
@original_method = stubbee.instance_method(method)
-
15
if @original_method && @original_method.owner == stubbee
-
15
@original_visibility = :public
-
15
if stubbee.protected_instance_methods.include?(method)
-
@original_visibility = :protected
-
elsif stubbee.private_instance_methods.include?(method)
-
@original_visibility = :private
-
end
-
15
stubbee.send(:remove_method, method)
-
end
-
rescue NameError
-
# deal with nasties like ActiveRecord::Associations::AssociationProxy
-
end
-
end
-
end
-
-
1
def define_new_method
-
21
stubbee.class_eval(%{
-
def #{method}(*args, &block)
-
self.class.any_instance.mocha.method_missing(:#{method}, *args, &block)
-
end
-
}, __FILE__, __LINE__)
-
end
-
-
1
def remove_new_method
-
21
stubbee.send(:remove_method, method)
-
end
-
-
1
def restore_original_method
-
21
if @original_method && @original_method.owner == stubbee
-
15
stubbee.send(:define_method, method, @original_method)
-
15
stubbee.send(@original_visibility, method)
-
end
-
end
-
-
1
def method_exists?(method)
-
21
return true if stubbee.public_instance_methods(false).include?(method)
-
6
return true if stubbee.protected_instance_methods(false).include?(method)
-
6
return true if stubbee.private_instance_methods(false).include?(method)
-
6
return false
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers'
-
1
require 'mocha/hooks'
-
1
require 'mocha/mockery'
-
1
require 'mocha/sequence'
-
1
require 'mocha/object_methods'
-
1
require 'mocha/module_methods'
-
1
require 'mocha/class_methods'
-
-
1
module Mocha
-
-
# Methods added to +Test::Unit::TestCase+, +MiniTest::Unit::TestCase+ or equivalent.
-
1
module API
-
-
1
include ParameterMatchers
-
1
include Hooks
-
-
# @private
-
1
def self.included(mod)
-
1
Object.send(:include, Mocha::ObjectMethods)
-
1
Module.send(:include, Mocha::ModuleMethods)
-
1
Class.send(:include, Mocha::ClassMethods)
-
end
-
-
# Builds a new mock object
-
#
-
# @param [String] name identifies mock object in error messages.
-
# @param [Hash] expected_methods_vs_return_values expected method name symbols as keys and corresponding return values as values - these expectations are setup as if {Mock#expects} were called multiple times.
-
# @yield optional block to be evaluated against the mock object instance, giving an alternative way to setup expectations.
-
# @return [Mock] a new mock object
-
#
-
# @overload def mock(name, &block)
-
# @overload def mock(expected_methods_vs_return_values = {}, &block)
-
# @overload def mock(name, expected_methods_vs_return_values = {}, &block)
-
#
-
# @example Using expected_methods_vs_return_values Hash to setup expectations.
-
# def test_motor_starts_and_stops
-
# motor = mock('motor', :start => true, :stop => true)
-
# assert motor.start
-
# assert motor.stop
-
# # an error will be raised unless both Motor#start and Motor#stop have been called
-
# end
-
# @example Using the optional block to setup expectations & stubbed methods.
-
# def test_motor_starts_and_stops
-
# motor = mock('motor') do
-
# expects(:start).with(100.rpm).returns(true)
-
# stubs(:stop).returns(true)
-
# end
-
# assert motor.start(100.rpm)
-
# assert motor.stop
-
# # an error will only be raised if Motor#start(100.rpm) has not been called
-
# end
-
1
def mock(*arguments, &block)
-
2
name = arguments.shift if arguments.first.is_a?(String)
-
2
expectations = arguments.shift || {}
-
2
mock = name ? Mockery.instance.named_mock(name, &block) : Mockery.instance.unnamed_mock(&block)
-
2
mock.expects(expectations)
-
2
mock
-
end
-
-
# Builds a new mock object
-
#
-
# @param [String] name identifies mock object in error messages.
-
# @param [Hash] stubbed_methods_vs_return_values stubbed method name symbols as keys and corresponding return values as values - these stubbed methods are setup as if {Mock#stubs} were called multiple times.
-
# @yield optional block to be evaluated against the mock object instance, giving an alternative way to setup stubbed methods.
-
# @return [Mock] a new mock object
-
#
-
# @overload def stub(name, &block)
-
# @overload def stub(stubbed_methods_vs_return_values = {}, &block)
-
# @overload def stub(name, stubbed_methods_vs_return_values = {}, &block)
-
#
-
# @example Using stubbed_methods_vs_return_values Hash to setup stubbed methods.
-
# def test_motor_starts_and_stops
-
# motor = mock('motor', :start => true, :stop => true)
-
# assert motor.start
-
# assert motor.stop
-
# # an error will not be raised even if either Motor#start or Motor#stop has not been called
-
# end
-
#
-
# @example Using the optional block to setup expectations & stubbed methods.
-
# def test_motor_starts_and_stops
-
# motor = mock('motor') do
-
# expects(:start).with(100.rpm).returns(true)
-
# stubs(:stop).returns(true)
-
# end
-
# assert motor.start(100.rpm)
-
# assert motor.stop
-
# # an error will only be raised if Motor#start(100.rpm) has not been called
-
# end
-
1
def stub(*arguments, &block)
-
636
name = arguments.shift if arguments.first.is_a?(String)
-
636
expectations = arguments.shift || {}
-
636
stub = name ? Mockery.instance.named_mock(name, &block) : Mockery.instance.unnamed_mock(&block)
-
636
stub.stubs(expectations)
-
636
stub
-
end
-
-
# Builds a mock object that accepts calls to any method. By default it will return +nil+ for any method call.
-
#
-
# @param [String] name identifies mock object in error messages.
-
# @param [Hash] stubbed_methods_vs_return_values stubbed method name symbols as keys and corresponding return values as values - these stubbed methods are setup as if {Mock#stubs} were called multiple times.
-
# @yield optional block to be evaluated against the mock object instance, giving an alternative way to setup stubbed methods.
-
# @return [Mock] a new mock object
-
#
-
# @overload def stub_everything(name, &block)
-
# @overload def stub_everything(stubbed_methods_vs_return_values = {}, &block)
-
# @overload def stub_everything(name, stubbed_methods_vs_return_values = {}, &block)
-
#
-
# @example Ignore invocations of irrelevant methods.
-
# def test_motor_stops
-
# motor = stub_everything('motor', :stop => true)
-
# assert_nil motor.irrelevant_method_1 # => no error raised
-
# assert_nil motor.irrelevant_method_2 # => no error raised
-
# assert motor.stop
-
# end
-
1
def stub_everything(*arguments, &block)
-
name = arguments.shift if arguments.first.is_a?(String)
-
expectations = arguments.shift || {}
-
stub = name ? Mockery.instance.named_mock(name, &block) : Mockery.instance.unnamed_mock(&block)
-
stub.stub_everything
-
stub.stubs(expectations)
-
stub
-
end
-
-
# Builds a new sequence which can be used to constrain the order in which expectations can occur.
-
#
-
# Specify that an expected invocation must occur within a named {Sequence} by using {Expectation#in_sequence}.
-
#
-
# @return [Sequence] a new sequence
-
#
-
# @see Expectation#in_sequence
-
#
-
# @example Ensure methods on egg are invoked in correct order.
-
# breakfast = sequence('breakfast')
-
#
-
# egg = mock('egg') do
-
# expects(:crack).in_sequence(breakfast)
-
# expects(:fry).in_sequence(breakfast)
-
# expects(:eat).in_sequence(breakfast)
-
# end
-
1
def sequence(name)
-
Sequence.new(name)
-
end
-
-
# Builds a new state machine which can be used to constrain the order in which expectations can occur.
-
#
-
# Specify the initial state of the state machine by using {StateMachine#starts_as}.
-
#
-
# Specify that an expected invocation should change the state of the state machine by using {Expectation#then}.
-
#
-
# Specify that an expected invocation should be constrained to occur within a particular +state+ by using {Expectation#when}.
-
#
-
# A test can contain multiple state machines.
-
#
-
# @return [StateMachine] a new state machine
-
#
-
# @see Expectation#then
-
# @see Expectation#when
-
# @see StateMachine
-
# @example Constrain expected invocations to occur in particular states.
-
# power = states('power').starts_as('off')
-
#
-
# radio = mock('radio') do
-
# expects(:switch_on).then(power.is('on'))
-
# expects(:select_channel).with('BBC Radio 4').when(power.is('on'))
-
# expects(:adjust_volume).with(+5).when(power.is('on'))
-
# expects(:select_channel).with('BBC World Service').when(power.is('on'))
-
# expects(:adjust_volume).with(-5).when(power.is('on'))
-
# expects(:switch_off).then(power.is('off'))
-
# end
-
1
def states(name)
-
Mockery.instance.new_state_machine(name)
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class ArgumentIterator
-
-
1
def initialize(argument)
-
2633
@argument = argument
-
end
-
-
1
def each(&block)
-
2633
if @argument.is_a?(Hash) then
-
639
@argument.each do |method_name, return_value|
-
1264
block.call(method_name, return_value)
-
end
-
else
-
1994
block.call(@argument)
-
end
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class BacktraceFilter
-
-
1
LIB_DIRECTORY = File.expand_path(File.join(File.dirname(__FILE__), "..")) + File::SEPARATOR
-
-
1
def initialize(lib_directory = LIB_DIRECTORY)
-
@path_pattern = Regexp.new(lib_directory)
-
end
-
-
1
def filtered(backtrace)
-
backtrace.reject { |location| @path_pattern.match(File.expand_path(location)) }
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class Cardinality
-
-
1
INFINITY = 1 / 0.0
-
-
1
class << self
-
-
1
def exactly(count)
-
2270
new(count, count)
-
end
-
-
1
def at_least(count)
-
2189
new(count, INFINITY)
-
end
-
-
1
def at_most(count)
-
new(0, count)
-
end
-
-
1
def times(range_or_count)
-
1
case range_or_count
-
when Range then new(range_or_count.first, range_or_count.last)
-
1
else new(range_or_count, range_or_count)
-
end
-
end
-
-
end
-
-
1
def initialize(required, maximum)
-
4460
@required, @maximum = required, maximum
-
end
-
-
1
def invocations_allowed?(invocation_count)
-
585
invocation_count < maximum
-
end
-
-
1
def satisfied?(invocations_so_far)
-
invocations_so_far >= required
-
end
-
-
1
def needs_verifying?
-
!allowed_any_number_of_times?
-
end
-
-
1
def verified?(invocation_count)
-
2263
(invocation_count >= required) && (invocation_count <= maximum)
-
end
-
-
1
def allowed_any_number_of_times?
-
required == 0 && infinite?(maximum)
-
end
-
-
1
def used?(invocation_count)
-
(invocation_count > 0) || (maximum == 0)
-
end
-
-
1
def mocha_inspect
-
if allowed_any_number_of_times?
-
"allowed any number of times"
-
else
-
if required == 0 && maximum == 0
-
"expected never"
-
elsif required == maximum
-
"expected exactly #{times(required)}"
-
elsif infinite?(maximum)
-
"expected at least #{times(required)}"
-
elsif required == 0
-
"expected at most #{times(maximum)}"
-
else
-
"expected between #{required} and #{times(maximum)}"
-
end
-
end
-
end
-
-
1
protected
-
-
1
attr_reader :required, :maximum
-
-
1
def times(number)
-
case number
-
when 0 then "no times"
-
when 1 then "once"
-
when 2 then "twice"
-
else "#{number} times"
-
end
-
end
-
-
1
def infinite?(number)
-
number.respond_to?(:infinite?) && number.infinite?
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class Central
-
-
1
attr_accessor :stubba_methods
-
-
1
def initialize
-
3948
self.stubba_methods = []
-
end
-
-
1
def stub(method)
-
1706
unless stubba_methods.detect { |m| m.matches?(method) }
-
463
method.stub
-
463
stubba_methods.push(method)
-
end
-
end
-
-
1
def unstub(method)
-
926
if existing = stubba_methods.detect { |m| m.matches?(method) }
-
463
existing.unstub
-
463
stubba_methods.delete(existing)
-
end
-
end
-
-
1
def unstub_all
-
3948
while stubba_methods.any? do
-
463
unstub(stubba_methods.first)
-
end
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class ChangeStateSideEffect
-
-
1
def initialize(state)
-
@state = state
-
end
-
-
1
def perform
-
@state.activate
-
end
-
-
1
def mocha_inspect
-
"then #{@state.mocha_inspect}"
-
end
-
-
end
-
-
end
-
1
require 'metaclass'
-
-
1
module Mocha
-
-
1
class ClassMethod
-
-
1
attr_reader :stubbee, :method
-
-
1
def initialize(stubbee, method)
-
995
@stubbee, @original_method = stubbee, nil
-
995
@method = RUBY_VERSION < '1.9' ? method.to_s : method.to_sym
-
end
-
-
1
def stub
-
463
hide_original_method
-
463
define_new_method
-
end
-
-
1
def unstub
-
463
remove_new_method
-
463
restore_original_method
-
463
mock.unstub(method.to_sym)
-
463
unless mock.any_expectations?
-
310
reset_mocha
-
end
-
end
-
-
1
def mock
-
884
stubbee.mocha
-
end
-
-
1
def reset_mocha
-
291
stubbee.reset_mocha
-
end
-
-
1
def hide_original_method
-
442
if method_exists?(method)
-
377
begin
-
377
@original_method = stubbee._method(method)
-
377
if @original_method && @original_method.owner == stubbee.__metaclass__
-
327
@original_visibility = :public
-
327
if stubbee.__metaclass__.protected_instance_methods.include?(method)
-
@original_visibility = :protected
-
elsif stubbee.__metaclass__.private_instance_methods.include?(method)
-
@original_visibility = :private
-
end
-
327
stubbee.__metaclass__.send(:remove_method, method)
-
end
-
rescue NameError
-
# deal with nasties like ActiveRecord::Associations::AssociationProxy
-
end
-
end
-
end
-
-
1
def define_new_method
-
442
stubbee.__metaclass__.class_eval(%{
-
def #{method}(*args, &block)
-
mocha.method_missing(:#{method}, *args, &block)
-
end
-
}, __FILE__, __LINE__)
-
end
-
-
1
def remove_new_method
-
442
stubbee.__metaclass__.send(:remove_method, method)
-
end
-
-
1
def restore_original_method
-
442
if @original_method && @original_method.owner == stubbee.__metaclass__
-
327
if RUBY_VERSION < '1.9'
-
original_method = @original_method
-
stubbee.__metaclass__.send(:define_method, method) do |*args, &block|
-
original_method.call(*args, &block)
-
end
-
else
-
327
stubbee.__metaclass__.send(:define_method, method, @original_method)
-
end
-
327
stubbee.__metaclass__.send(@original_visibility, method)
-
end
-
end
-
-
1
def matches?(other)
-
1174
return false unless (other.class == self.class)
-
1173
(stubbee.object_id == other.stubbee.object_id) and (method == other.method)
-
end
-
-
1
alias_method :==, :eql?
-
-
1
def to_s
-
"#{stubbee}.#{method}"
-
end
-
-
1
def method_exists?(method)
-
265
symbol = method.to_sym
-
265
__metaclass__ = stubbee.__metaclass__
-
265
__metaclass__.public_method_defined?(symbol) || __metaclass__.protected_method_defined?(symbol) || __metaclass__.private_method_defined?(symbol)
-
end
-
-
end
-
-
end
-
1
require 'mocha/mockery'
-
1
require 'mocha/class_method'
-
1
require 'mocha/any_instance_method'
-
-
1
module Mocha
-
-
# Methods added to all classes to allow mocking and stubbing on real (i.e. non-mock) objects.
-
1
module ClassMethods
-
-
# @private
-
1
def stubba_method
-
766
Mocha::ClassMethod
-
end
-
-
# @private
-
1
class AnyInstance
-
-
1
def initialize(klass)
-
2
@stubba_object = klass
-
end
-
-
1
def mocha
-
89
@mocha ||= Mocha::Mockery.instance.mock_impersonating_any_instance_of(@stubba_object)
-
end
-
-
1
def stubba_method
-
21
Mocha::AnyInstanceMethod
-
end
-
-
1
def stubba_object
-
21
@stubba_object
-
end
-
-
1
def method_exists?(method, include_public_methods = true)
-
if include_public_methods
-
return true if @stubba_object.public_instance_methods(include_superclass_methods = true).include?(method)
-
end
-
return true if @stubba_object.protected_instance_methods(include_superclass_methods = true).include?(method)
-
return true if @stubba_object.private_instance_methods(include_superclass_methods = true).include?(method)
-
return false
-
end
-
-
end
-
-
# @return [Mock] a mock object which will detect calls to any instance of this class.
-
# @raise [StubbingError] if attempting to stub method which is not allowed.
-
#
-
# @example Return false to invocation of +Product#save+ for any instance of +Product+.
-
# Product.any_instance.stubs(:save).returns(false)
-
# product_1 = Product.new
-
# assert_equal false, product_1.save
-
# product_2 = Product.new
-
# assert_equal false, product_2.save
-
1
def any_instance
-
108
if frozen?
-
raise StubbingError.new("can't stub method on frozen object: #{mocha_inspect}.any_instance", caller)
-
end
-
108
@any_instance ||= AnyInstance.new(self)
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
# Configuration settings.
-
1
class Configuration
-
-
1
DEFAULTS = {
-
:stubbing_method_unnecessarily => :allow,
-
:stubbing_method_on_non_mock_object => :allow,
-
:stubbing_non_existent_method => :allow,
-
:stubbing_non_public_method => :allow,
-
:stubbing_method_on_nil => :prevent,
-
}
-
-
1
class << self
-
-
# Allow the specified +action+.
-
#
-
# @param [Symbol] action one of +:stubbing_method_unnecessarily+, +:stubbing_method_on_non_mock_object+, +:stubbing_non_existent_method+, +:stubbing_non_public_method+, +:stubbing_method_on_nil+.
-
# @yield optional block during which the configuration change will be changed before being returned to its original value at the end of the block.
-
1
def allow(action, &block)
-
change_config action, :allow, &block
-
end
-
-
# @private
-
1
def allow?(action)
-
6243
configuration[action] == :allow
-
end
-
-
# Warn if the specified +action+ is attempted.
-
#
-
# @param [Symbol] action one of +:stubbing_method_unnecessarily+, +:stubbing_method_on_non_mock_object+, +:stubbing_non_existent_method+, +:stubbing_non_public_method+, +:stubbing_method_on_nil+.
-
# @yield optional block during which the configuration change will be changed before being returned to its original value at the end of the block.
-
1
def warn_when(action, &block)
-
change_config action, :warn, &block
-
end
-
-
# @private
-
1
def warn_when?(action)
-
configuration[action] == :warn
-
end
-
-
# Raise a {StubbingError} if if the specified +action+ is attempted.
-
#
-
# @param [Symbol] action one of +:stubbing_method_unnecessarily+, +:stubbing_method_on_non_mock_object+, +:stubbing_non_existent_method+, +:stubbing_non_public_method+, +:stubbing_method_on_nil+.
-
# @yield optional block during which the configuration change will be changed before being returned to its original value at the end of the block.
-
1
def prevent(action, &block)
-
change_config action, :prevent, &block
-
end
-
-
# @private
-
1
def prevent?(action)
-
configuration[action] == :prevent
-
end
-
-
# @private
-
1
def reset_configuration
-
@configuration = nil
-
end
-
-
1
private
-
-
# @private
-
1
def configuration
-
6243
@configuration ||= DEFAULTS.dup
-
end
-
-
# @private
-
1
def change_config(action, new_value, &block)
-
if block_given?
-
temporarily_change_config action, new_value, &block
-
else
-
configuration[action] = new_value
-
end
-
end
-
-
# @private
-
1
def temporarily_change_config(action, new_value, &block)
-
original_value = configuration[action]
-
configuration[action] = new_value
-
yield
-
ensure
-
configuration[action] = original_value
-
end
-
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class ExceptionRaiser
-
-
1
def initialize(exception, message)
-
@exception, @message = exception, message
-
end
-
-
1
def evaluate
-
raise @exception, @exception.to_s if @exception.is_a?(Module) && (@exception < Interrupt)
-
raise @exception, @message if @message
-
raise @exception
-
end
-
-
end
-
-
end
-
1
require 'mocha/method_matcher'
-
1
require 'mocha/parameters_matcher'
-
1
require 'mocha/expectation_error'
-
1
require 'mocha/return_values'
-
1
require 'mocha/exception_raiser'
-
1
require 'mocha/thrower'
-
1
require 'mocha/yield_parameters'
-
1
require 'mocha/is_a'
-
1
require 'mocha/in_state_ordering_constraint'
-
1
require 'mocha/change_state_side_effect'
-
1
require 'mocha/cardinality'
-
-
1
module Mocha
-
-
# Methods on expectations returned from {Mock#expects}, {Mock#stubs}, {ObjectMethods#expects} and {ObjectMethods#stubs}.
-
1
class Expectation
-
-
# Modifies expectation so that the number of calls to the expected method must be within a specific +range+.
-
#
-
# @param [Range,Integer] range specifies the allowable range in the number of expected invocations.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Specifying a specific number of expected invocations.
-
# object = mock()
-
# object.expects(:expected_method).times(3)
-
# 3.times { object.expected_method }
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).times(3)
-
# 2.times { object.expected_method }
-
# # => verify fails
-
#
-
# @example Specifying a range in the number of expected invocations.
-
# object = mock()
-
# object.expects(:expected_method).times(2..4)
-
# 3.times { object.expected_method }
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).times(2..4)
-
# object.expected_method
-
# # => verify fails
-
1
def times(range)
-
1
@cardinality = Cardinality.times(range)
-
1
self
-
end
-
-
# Modifies expectation so that the expected method must be called exactly twice.
-
#
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Expected method must be invoked exactly twice.
-
# object = mock()
-
# object.expects(:expected_method).twice
-
# object.expected_method
-
# object.expected_method
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).twice
-
# object.expected_method
-
# object.expected_method
-
# object.expected_method # => unexpected invocation
-
#
-
# object = mock()
-
# object.expects(:expected_method).twice
-
# object.expected_method
-
# # => verify fails
-
1
def twice
-
2
@cardinality = Cardinality.exactly(2)
-
2
self
-
end
-
-
# Modifies expectation so that the expected method must be called exactly once.
-
#
-
# Note that this is the default behaviour for an expectation, but you may wish to use it for clarity/emphasis.
-
#
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Expected method must be invoked exactly once.
-
# object = mock()
-
# object.expects(:expected_method).once
-
# object.expected_method
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).once
-
# object.expected_method
-
# object.expected_method # => unexpected invocation
-
#
-
# object = mock()
-
# object.expects(:expected_method).once
-
# # => verify fails
-
1
def once
-
3
@cardinality = Cardinality.exactly(1)
-
3
self
-
end
-
-
# Modifies expectation so that the expected method must never be called.
-
#
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Expected method must never be called.
-
# object = mock()
-
# object.expects(:expected_method).never
-
# object.expected_method # => unexpected invocation
-
#
-
# object = mock()
-
# object.expects(:expected_method).never
-
# # => verify succeeds
-
1
def never
-
2
@cardinality = Cardinality.exactly(0)
-
2
self
-
end
-
-
# Modifies expectation so that the expected method must be called at least a +minimum_number_of_times+.
-
#
-
# @param [Integer] minimum_number_of_times minimum number of expected invocations.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Expected method must be called at least twice.
-
# object = mock()
-
# object.expects(:expected_method).at_least(2)
-
# 3.times { object.expected_method }
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).at_least(2)
-
# object.expected_method
-
# # => verify fails
-
1
def at_least(minimum_number_of_times)
-
2189
@cardinality = Cardinality.at_least(minimum_number_of_times)
-
2189
self
-
end
-
-
# Modifies expectation so that the expected method must be called at least once.
-
#
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Expected method must be called at least once.
-
# object = mock()
-
# object.expects(:expected_method).at_least_once
-
# object.expected_method
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).at_least_once
-
# # => verify fails
-
1
def at_least_once
-
19
at_least(1)
-
19
self
-
end
-
-
# Modifies expectation so that the expected method must be called at most a +maximum_number_of_times+.
-
#
-
# @param [Integer] maximum_number_of_times maximum number of expected invocations.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Expected method must be called at most twice.
-
# object = mock()
-
# object.expects(:expected_method).at_most(2)
-
# 2.times { object.expected_method }
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).at_most(2)
-
# 3.times { object.expected_method } # => unexpected invocation
-
1
def at_most(maximum_number_of_times)
-
@cardinality = Cardinality.at_most(maximum_number_of_times)
-
self
-
end
-
-
# Modifies expectation so that the expected method must be called at most once.
-
#
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Expected method must be called at most once.
-
# object = mock()
-
# object.expects(:expected_method).at_most_once
-
# object.expected_method
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).at_most_once
-
# 2.times { object.expected_method } # => unexpected invocation
-
1
def at_most_once()
-
at_most(1)
-
self
-
end
-
-
# Modifies expectation so that the expected method must be called with +expected_parameters+.
-
#
-
# May be used with parameter matchers in {ParameterMatchers}.
-
#
-
# @param [*Array] expected_parameters parameters expected.
-
# @yield optional block specifying custom matching.
-
# @yieldparam [*Array] actual_parameters parameters with which expected method was invoked.
-
# @yieldreturn [Boolean] +true+ if +actual_parameters+ are acceptable.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Expected method must be called with expected parameters.
-
# object = mock()
-
# object.expects(:expected_method).with(:param1, :param2)
-
# object.expected_method(:param1, :param2)
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).with(:param1, :param2)
-
# object.expected_method(:param3)
-
# # => verify fails
-
#
-
# @example Expected method must be called with a value divisible by 4.
-
# object = mock()
-
# object.expects(:expected_method).with() { |value| value % 4 == 0 }
-
# object.expected_method(16)
-
# # => verify succeeds
-
#
-
# object = mock()
-
# object.expects(:expected_method).with() { |value| value % 4 == 0 }
-
# object.expected_method(17)
-
# # => verify fails
-
1
def with(*expected_parameters, &matching_block)
-
703
@parameters_matcher = ParametersMatcher.new(expected_parameters, &matching_block)
-
703
self
-
end
-
-
# Modifies expectation so that when the expected method is called, it yields with the specified +parameters+.
-
#
-
# May be called multiple times on the same expectation for consecutive invocations.
-
#
-
# @param [*Array] parameters parameters to be yielded.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
# @see #then
-
#
-
# @example Yield parameters when expected method is invoked.
-
# object = mock()
-
# object.expects(:expected_method).yields('result')
-
# yielded_value = nil
-
# object.expected_method { |value| yielded_value = value }
-
# yielded_value # => 'result'
-
#
-
# @example Yield different parameters on different invocations of the expected method.
-
# object = mock()
-
# object.stubs(:expected_method).yields(1).then.yields(2)
-
# yielded_values_from_first_invocation = []
-
# yielded_values_from_second_invocation = []
-
# object.expected_method { |value| yielded_values_from_first_invocation << value } # first invocation
-
# object.expected_method { |value| yielded_values_from_second_invocation << value } # second invocation
-
# yielded_values_from_first_invocation # => [1]
-
# yielded_values_from_second_invocation # => [2]
-
1
def yields(*parameters)
-
@yield_parameters.add(*parameters)
-
self
-
end
-
-
# Modifies expectation so that when the expected method is called, it yields multiple times per invocation with the specified +parameter_groups+.
-
#
-
# @param [*Array<Array>] parameter_groups each element of +parameter_groups+ should iself be an +Array+ representing the parameters to be passed to the block for a single yield.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
# @see #then
-
#
-
# @example When the +expected_method+ is called, the stub will invoke the block twice, the first time it passes +'result_1'+, +'result_2'+ as the parameters, and the second time it passes 'result_3' as the parameters.
-
# object = mock()
-
# object.expects(:expected_method).multiple_yields(['result_1', 'result_2'], ['result_3'])
-
# yielded_values = []
-
# object.expected_method { |*values| yielded_values << values }
-
# yielded_values # => [['result_1', 'result_2'], ['result_3]]
-
#
-
# @example Yield different groups of parameters on different invocations of the expected method.
-
# object = mock()
-
# object.stubs(:expected_method).multiple_yields([1, 2], [3]).then.multiple_yields([4], [5, 6])
-
# yielded_values_from_first_invocation = []
-
# yielded_values_from_second_invocation = []
-
# object.expected_method { |*values| yielded_values_from_first_invocation << values } # first invocation
-
# object.expected_method { |*values| yielded_values_from_second_invocation << values } # second invocation
-
# yielded_values_from_first_invocation # => [[1, 2], [3]]
-
# yielded_values_from_second_invocation # => [[4], [5, 6]]
-
1
def multiple_yields(*parameter_groups)
-
@yield_parameters.multiple_add(*parameter_groups)
-
self
-
end
-
-
# Modifies expectation so that when the expected method is called, it returns the specified +value+.
-
#
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
# @see #then
-
#
-
# @overload def returns(value)
-
# @param [Object] value value to return on invocation of expected method.
-
# @overload def returns(*values)
-
# @param [*Array] values values to return on consecutive invocations of expected method.
-
#
-
# @example Return the same value on every invocation.
-
# object = mock()
-
# object.stubs(:stubbed_method).returns('result')
-
# object.stubbed_method # => 'result'
-
# object.stubbed_method # => 'result'
-
#
-
# @example Return a different value on consecutive invocations.
-
# object = mock()
-
# object.stubs(:stubbed_method).returns(1, 2)
-
# object.stubbed_method # => 1
-
# object.stubbed_method # => 2
-
#
-
# @example Alternative way to return a different value on consecutive invocations.
-
# object = mock()
-
# object.stubs(:expected_method).returns(1, 2).then.returns(3)
-
# object.expected_method # => 1
-
# object.expected_method # => 2
-
# object.expected_method # => 3
-
#
-
# @example May be called in conjunction with {#raises} on the same expectation.
-
# object = mock()
-
# object.stubs(:expected_method).returns(1, 2).then.raises(Exception)
-
# object.expected_method # => 1
-
# object.expected_method # => 2
-
# object.expected_method # => raises exception of class Exception1
-
#
-
# @example Note that in Ruby a method returning multiple values is exactly equivalent to a method returning an +Array+ of those values.
-
# object = mock()
-
# object.stubs(:expected_method).returns([1, 2])
-
# x, y = object.expected_method
-
# x # => 1
-
# y # => 2
-
1
def returns(*values)
-
2209
@return_values += ReturnValues.build(*values)
-
2209
self
-
end
-
-
# Modifies expectation so that when the expected method is called, it raises the specified +exception+ with the specified +message+ i.e. calls +Kernel#raise(exception, message)+.
-
#
-
# @param [Class,Exception,String,#exception] exception exception to be raised or message to be passed to RuntimeError.
-
# @param [String] message exception message.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @see Kernel#raise
-
# @see #then
-
#
-
# @overload def raises
-
# @overload def raises(exception)
-
# @overload def raises(exception, message)
-
#
-
# @example Raise specified exception if expected method is invoked.
-
# object = stub()
-
# object.stubs(:expected_method).raises(Exception, 'message')
-
# object.expected_method # => raises exception of class Exception and with message 'message'
-
#
-
# @example Raise custom exception with extra constructor parameters by passing in an instance of the exception.
-
# object = stub()
-
# object.stubs(:expected_method).raises(MyException.new('message', 1, 2, 3))
-
# object.expected_method # => raises the specified instance of MyException
-
#
-
# @example Raise different exceptions on consecutive invocations of the expected method.
-
# object = stub()
-
# object.stubs(:expected_method).raises(Exception1).then.raises(Exception2)
-
# object.expected_method # => raises exception of class Exception1
-
# object.expected_method # => raises exception of class Exception2
-
#
-
# @example Raise an exception on first invocation of expected method and then return values on subsequent invocations.
-
# object = stub()
-
# object.stubs(:expected_method).raises(Exception).then.returns(2, 3)
-
# object.expected_method # => raises exception of class Exception1
-
# object.expected_method # => 2
-
# object.expected_method # => 3
-
1
def raises(exception = RuntimeError, message = nil)
-
@return_values += ReturnValues.new(ExceptionRaiser.new(exception, message))
-
self
-
end
-
-
# Modifies expectation so that when the expected method is called, it throws the specified +tag+ with the specific return value +object+ i.e. calls +Kernel#throw(tag, object)+.
-
#
-
# @param [Symbol,String] tag tag to throw to transfer control to the active catch block.
-
# @param [Object] object return value for the catch block.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @see Kernel#throw
-
# @see #then
-
#
-
# @overload def throw(tag)
-
# @overload def throw(tag, object)
-
#
-
# @example Throw tag when expected method is invoked.
-
# object = stub()
-
# object.stubs(:expected_method).throws(:done)
-
# object.expected_method # => throws tag :done
-
#
-
# @example Throw tag with return value +object+ c.f. +Kernel#throw+.
-
# object = stub()
-
# object.stubs(:expected_method).throws(:done, 'result')
-
# object.expected_method # => throws tag :done and causes catch block to return 'result'
-
#
-
# @example Throw different tags on consecutive invocations of the expected method.
-
# object = stub()
-
# object.stubs(:expected_method).throws(:done).then.throws(:continue)
-
# object.expected_method # => throws :done
-
# object.expected_method # => throws :continue
-
#
-
# @example Throw tag on first invocation of expected method and then return values for subsequent invocations.
-
# object = stub()
-
# object.stubs(:expected_method).throws(:done).then.returns(2, 3)
-
# object.expected_method # => throws :done
-
# object.expected_method # => 2
-
# object.expected_method # => 3
-
1
def throws(tag, object = nil)
-
@return_values += ReturnValues.new(Thrower.new(tag, object))
-
self
-
end
-
-
# @overload def then
-
# Used as syntactic sugar to improve readability. It has no effect on state of the expectation.
-
# @overload def then(state_machine.is(state_name))
-
# Used to change the +state_machine+ to the state specified by +state_name+ when the expected invocation occurs.
-
# @param [StateMachine::State] state_machine.is(state_name) provides a mechanism to change the +state_machine+ into the state specified by +state_name+ when the expected method is invoked.
-
#
-
# @see API#states
-
# @see StateMachine
-
# @see #when
-
#
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @example Using {#then} as syntactic sugar when specifying values to be returned and exceptions to be raised on consecutive invocations of the expected method.
-
# object = mock()
-
# object.stubs(:expected_method).returns(1, 2).then.raises(Exception).then.returns(4)
-
# object.expected_method # => 1
-
# object.expected_method # => 2
-
# object.expected_method # => raises exception of class Exception
-
# object.expected_method # => 4
-
#
-
# @example Using {#then} to change the +state+ of a +state_machine+ on the invocation of an expected method.
-
# power = states('power').starts_as('off')
-
#
-
# radio = mock('radio')
-
# radio.expects(:switch_on).then(power.is('on'))
-
# radio.expects(:select_channel).with('BBC Radio 4').when(power.is('on'))
-
# radio.expects(:adjust_volume).with(+5).when(power.is('on'))
-
# radio.expects(:select_channel).with('BBC World Service').when(power.is('on'))
-
# radio.expects(:adjust_volume).with(-5).when(power.is('on'))
-
# radio.expects(:switch_off).then(power.is('off'))
-
1
def then(*parameters)
-
if parameters.length == 1
-
state = parameters.first
-
add_side_effect(ChangeStateSideEffect.new(state))
-
end
-
self
-
end
-
-
# Constrains the expectation to occur only when the +state_machine+ is in the state specified by +state_name+.
-
#
-
# @param [StateMachine::StatePredicate] state_machine.is(state_name) provides a mechanism to determine whether the +state_machine+ is in the state specified by +state_name+ when the expected method is invoked.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @see API#states
-
# @see StateMachine
-
# @see #then
-
#
-
# @example Using {#when} to only allow invocation of methods when "power" state machine is in the "on" state.
-
# power = states('power').starts_as('off')
-
#
-
# radio = mock('radio')
-
# radio.expects(:switch_on).then(power.is('on'))
-
# radio.expects(:select_channel).with('BBC Radio 4').when(power.is('on'))
-
# radio.expects(:adjust_volume).with(+5).when(power.is('on'))
-
# radio.expects(:select_channel).with('BBC World Service').when(power.is('on'))
-
# radio.expects(:adjust_volume).with(-5).when(power.is('on'))
-
# radio.expects(:switch_off).then(power.is('off'))
-
1
def when(state_predicate)
-
add_ordering_constraint(InStateOrderingConstraint.new(state_predicate))
-
self
-
end
-
-
# Constrains the expectation so that it must be invoked at the current point in the +sequence+.
-
#
-
# To expect a sequence of invocations, write the expectations in order and add the +in_sequence(sequence)+ clause to each one.
-
#
-
# Expectations in a +sequence+ can have any invocation count.
-
#
-
# If an expectation in a sequence is stubbed, rather than expected, it can be skipped in the +sequence+.
-
#
-
# An expected method can appear in multiple sequences.
-
#
-
# @param [*Array<Sequence>] sequences sequences in which expected method should appear.
-
# @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
-
#
-
# @see API#sequence
-
#
-
# @example Ensure methods are invoked in a specified order.
-
# breakfast = sequence('breakfast')
-
#
-
# egg = mock('egg')
-
# egg.expects(:crack).in_sequence(breakfast)
-
# egg.expects(:fry).in_sequence(breakfast)
-
# egg.expects(:eat).in_sequence(breakfast)
-
1
def in_sequence(*sequences)
-
sequences.each { |sequence| add_in_sequence_ordering_constraint(sequence) }
-
self
-
end
-
-
# @private
-
1
attr_reader :backtrace
-
-
# @private
-
1
def initialize(mock, expected_method_name, backtrace = nil)
-
2263
@mock = mock
-
2263
@method_matcher = MethodMatcher.new(expected_method_name.to_sym)
-
2263
@parameters_matcher = ParametersMatcher.new
-
2263
@ordering_constraints = []
-
2263
@side_effects = []
-
2263
@cardinality, @invocation_count = Cardinality.exactly(1), 0
-
2263
@return_values = ReturnValues.new
-
2263
@yield_parameters = YieldParameters.new
-
2263
@backtrace = backtrace || caller
-
end
-
-
# @private
-
1
def add_ordering_constraint(ordering_constraint)
-
@ordering_constraints << ordering_constraint
-
end
-
-
# @private
-
1
def add_in_sequence_ordering_constraint(sequence)
-
sequence.constrain_as_next_in_sequence(self)
-
end
-
-
# @private
-
1
def add_side_effect(side_effect)
-
@side_effects << side_effect
-
end
-
-
# @private
-
1
def perform_side_effects
-
584
@side_effects.each { |side_effect| side_effect.perform }
-
end
-
-
# @private
-
1
def in_correct_order?
-
599
@ordering_constraints.all? { |ordering_constraint| ordering_constraint.allows_invocation_now? }
-
end
-
-
# @private
-
1
def matches_method?(method_name)
-
1154
@method_matcher.match?(method_name)
-
end
-
-
# @private
-
1
def match?(actual_method_name, *actual_parameters)
-
1269
@method_matcher.match?(actual_method_name) && @parameters_matcher.match?(actual_parameters) && in_correct_order?
-
end
-
-
# @private
-
1
def invocations_allowed?
-
585
@cardinality.invocations_allowed?(@invocation_count)
-
end
-
-
# @private
-
1
def satisfied?
-
@cardinality.satisfied?(@invocation_count)
-
end
-
-
# @private
-
1
def invoke
-
584
@invocation_count += 1
-
584
perform_side_effects()
-
584
if block_given? then
-
@yield_parameters.next_invocation.each do |yield_parameters|
-
yield(*yield_parameters)
-
end
-
end
-
584
@return_values.next
-
end
-
-
# @private
-
1
def verified?(assertion_counter = nil)
-
2263
assertion_counter.increment if assertion_counter && @cardinality.needs_verifying?
-
2263
@cardinality.verified?(@invocation_count)
-
end
-
-
# @private
-
1
def used?
-
@cardinality.used?(@invocation_count)
-
end
-
-
# @private
-
1
def mocha_inspect
-
message = "#{@cardinality.mocha_inspect}, "
-
message << case @invocation_count
-
when 0 then "not yet invoked"
-
when 1 then "invoked once"
-
when 2 then "invoked twice"
-
else "invoked #{@invocation_count} times"
-
end
-
message << ": "
-
message << method_signature
-
message << "; #{@ordering_constraints.map { |oc| oc.mocha_inspect }.join("; ")}" unless @ordering_constraints.empty?
-
message
-
end
-
-
# @private
-
1
def method_signature
-
"#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}#{@parameters_matcher.mocha_inspect}"
-
end
-
-
end
-
-
end
-
1
module Mocha
-
# Default exception class raised when an unexpected invocation or an unsatisfied expectation occurs.
-
#
-
# Authors of test libraries may use +Mocha::ExpectationErrorFactory+ to have Mocha raise a different exception.
-
#
-
# @see Mocha::ExpectationErrorFactory
-
1
class ExpectationError < Exception; end
-
end
-
1
require 'mocha/backtrace_filter'
-
1
require 'mocha/expectation_error'
-
-
1
module Mocha
-
-
# This factory determines what class of exception should be raised when Mocha detects a test failure.
-
#
-
# This class should only be used by authors of test libraries and not by typical "users" of Mocha.
-
#
-
# For example, it is used by +Mocha::Integration::MiniTest::Adapter+ in order to have Mocha raise a +MiniTest::Assertion+ which can then be sensibly handled by +MiniTest::Unit::TestCase+.
-
#
-
# @see Mocha::Integration::MiniTest::Adapter
-
1
class ExpectationErrorFactory
-
1
class << self
-
# @!attribute exception_class
-
# Determines what class of exception should be raised when Mocha detects a test failure.
-
#
-
# This attribute may be set by authors of test libraries in order to have Mocha raise exceptions of a specific class when there is an unexpected invocation or an unsatisfied expectation.
-
#
-
# By default a +Mocha::ExpectationError+ will be raised.
-
#
-
# @return [Exception] class of exception to be raised when an expectation error occurs
-
# @see Mocha::ExpectationError
-
1
attr_accessor :exception_class
-
-
# @private
-
1
def build(message = nil, backtrace = [])
-
self.exception_class ||= ExpectationError
-
exception = exception_class.new(message)
-
filter = BacktraceFilter.new
-
exception.set_backtrace(filter.filtered(backtrace))
-
exception
-
end
-
end
-
end
-
end
-
1
module Mocha
-
-
1
class ExpectationList
-
-
1
def initialize
-
948
@expectations = []
-
end
-
-
1
def add(expectation)
-
2263
@expectations.unshift(expectation)
-
2263
expectation
-
end
-
-
1
def remove_all_matching_method(method_name)
-
1613
@expectations.reject! { |expectation| expectation.matches_method?(method_name) }
-
end
-
-
1
def matches_method?(method_name)
-
8
@expectations.any? { |expectation| expectation.matches_method?(method_name) }
-
end
-
-
1
def match(method_name, *arguments)
-
matching_expectations(method_name, *arguments).first
-
end
-
-
1
def match_allowing_invocation(method_name, *arguments)
-
1169
matching_expectations(method_name, *arguments).detect { |e| e.invocations_allowed? }
-
end
-
-
1
def verified?(assertion_counter = nil)
-
3211
@expectations.all? { |expectation| expectation.verified?(assertion_counter) }
-
end
-
-
1
def to_a
-
948
@expectations
-
end
-
-
1
def to_set
-
@expectations.to_set
-
end
-
-
1
def length
-
@expectations.length
-
end
-
-
1
def any?
-
463
@expectations.any?
-
end
-
-
1
private
-
-
1
def matching_expectations(method_name, *arguments)
-
1853
@expectations.select { |e| e.match?(method_name, *arguments) }
-
end
-
-
end
-
-
end
-
1
require 'mocha/mockery'
-
-
1
module Mocha
-
-
# Integration hooks for test library authors.
-
#
-
# The methods in this module should be called from test libraries wishing to integrate with Mocha.
-
#
-
# This module is provided as part of the +Mocha::API+ module and is therefore part of the public API, but should only be used by authors of test libraries and not by typical "users" of Mocha.
-
#
-
# Integration with Test::Unit and MiniTest are provided as part of Mocha, because they are (or were once) part of the Ruby standard library. Integration with other test libraries is not provided as *part* of Mocha, but is supported by means of the methods in this module.
-
#
-
# See the code in the +Adapter+ modules for examples of how to use the methods in this module. +Mocha::ExpectationErrorFactory+ may be used if you want +Mocha+ to raise a different type of exception.
-
#
-
# @see Mocha::Integration::TestUnit::Adapter
-
# @see Mocha::Integration::MiniTest::Adapter
-
# @see Mocha::ExpectationErrorFactory
-
# @see Mocha::API
-
1
module Hooks
-
# Prepares Mocha before a test (only for use by authors of test libraries).
-
#
-
# This method should be called before each individual test starts (including before any "setup" code).
-
1
def mocha_setup
-
end
-
-
# Verifies that all mock expectations have been met (only for use by authors of test libraries).
-
#
-
# This is equivalent to a series of "assertions".
-
#
-
# This method should be called at the end of each individual test, before it has been determined whether or not the test has passed.
-
1
def mocha_verify(assertion_counter = nil)
-
3948
Mockery.instance.verify(assertion_counter)
-
end
-
-
# Resets Mocha after a test (only for use by authors of test libraries).
-
#
-
# This method should be called after each individual test has finished (including after any "teardown" code).
-
1
def mocha_teardown
-
3948
Mockery.instance.teardown
-
3948
Mockery.reset_instance
-
end
-
end
-
end
-
1
module Mocha
-
-
1
class InStateOrderingConstraint
-
-
1
def initialize(state_predicate)
-
@state_predicate = state_predicate
-
end
-
-
1
def allows_invocation_now?
-
@state_predicate.active?
-
end
-
-
1
def mocha_inspect
-
"when #{@state_predicate.mocha_inspect}"
-
end
-
-
end
-
-
end
-
1
require 'date'
-
-
1
module Mocha
-
-
1
module ObjectMethods
-
1
def mocha_inspect
-
address = self.__id__ * 2
-
address += 0x100000000 if address < 0
-
inspect =~ /#</ ? "#<#{self.class}:0x#{'%x' % address}>" : inspect
-
end
-
end
-
-
1
module StringMethods
-
1
def mocha_inspect
-
inspect.gsub(/\"/, "'")
-
end
-
end
-
-
1
module ArrayMethods
-
1
def mocha_inspect
-
"[#{collect { |member| member.mocha_inspect }.join(', ')}]"
-
end
-
end
-
-
1
module HashMethods
-
1
def mocha_inspect
-
"{#{collect { |key, value| "#{key.mocha_inspect} => #{value.mocha_inspect}" }.join(', ')}}"
-
end
-
end
-
-
1
module TimeMethods
-
1
def mocha_inspect
-
"#{inspect} (#{to_f} secs)"
-
end
-
end
-
-
1
module DateMethods
-
1
def mocha_inspect
-
to_s
-
end
-
end
-
-
end
-
-
1
class Object
-
1
include Mocha::ObjectMethods
-
end
-
-
1
class String
-
1
include Mocha::StringMethods
-
end
-
-
1
class Array
-
1
include Mocha::ArrayMethods
-
end
-
-
1
class Hash
-
1
include Mocha::HashMethods
-
end
-
-
1
class Time
-
1
include Mocha::TimeMethods
-
end
-
-
1
class Date
-
1
include Mocha::DateMethods
-
end
-
1
require 'mocha/class_method'
-
-
1
module Mocha
-
-
1
class InstanceMethod < ClassMethod
-
-
1
def method_exists?(method)
-
70
return true if stubbee.public_methods(false).include?(method)
-
55
return true if stubbee.protected_methods(false).include?(method)
-
53
return true if stubbee.private_methods(false).include?(method)
-
36
return false
-
end
-
-
end
-
-
end
-
1
class Object
-
-
# :stopdoc:
-
-
1
alias_method :__is_a__, :is_a?
-
-
# :startdoc:
-
-
end
-
1
module Mocha
-
-
1
class Logger
-
-
1
def initialize(io)
-
@io = io
-
end
-
-
1
def warn(message)
-
@io.puts "WARNING: #{message}"
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class MethodMatcher
-
-
1
attr_reader :expected_method_name
-
-
1
def initialize(expected_method_name)
-
2263
@expected_method_name = expected_method_name
-
end
-
-
1
def match?(actual_method_name)
-
2423
@expected_method_name == actual_method_name.to_sym
-
end
-
-
1
def mocha_inspect
-
"#{@expected_method_name}"
-
end
-
-
end
-
-
end
-
1
require 'metaclass'
-
1
require 'mocha/expectation'
-
1
require 'mocha/expectation_list'
-
1
require 'mocha/names'
-
1
require 'mocha/method_matcher'
-
1
require 'mocha/parameters_matcher'
-
1
require 'mocha/unexpected_invocation'
-
1
require 'mocha/argument_iterator'
-
1
require 'mocha/expectation_error_factory'
-
-
1
module Mocha
-
-
# Traditional mock object.
-
#
-
# All methods return an {Expectation} which can be further modified by methods on {Expectation}.
-
1
class Mock
-
-
# Adds an expectation that the specified method must be called exactly once with any parameters.
-
#
-
# @param [Symbol,String] method_name name of expected method
-
# @param [Hash] expected_methods_vs_return_values expected method name symbols as keys and corresponding return values as values - these expectations are setup as if {#expects} were called multiple times.
-
#
-
# @overload def expects(method_name)
-
# @overload def expects(expected_methods_vs_return_values)
-
# @return [Expectation] last-built expectation which can be further modified by methods on {Expectation}.
-
#
-
# @example Expected method invoked once so no error raised
-
# object = mock()
-
# object.expects(:expected_method)
-
# object.expected_method
-
#
-
# @example Expected method not invoked so error raised
-
# object = mock()
-
# object.expects(:expected_method)
-
# # error raised when test completes, because expected_method not called exactly once
-
#
-
# @example Expected method invoked twice so error raised
-
# object = mock()
-
# object.expects(:expected_method)
-
# object.expected_method
-
# object.expected_method # => error raised when expected method invoked second time
-
#
-
# @example Setup multiple expectations using +expected_methods_vs_return_values+.
-
# object = mock()
-
# object.expects(:expected_method_one => :result_one, :expected_method_two => :result_two)
-
#
-
# # is exactly equivalent to
-
#
-
# object = mock()
-
# object.expects(:expected_method_one).returns(:result_one)
-
# object.expects(:expected_method_two).returns(:result_two)
-
1
def expects(method_name_or_hash, backtrace = nil)
-
99
iterator = ArgumentIterator.new(method_name_or_hash)
-
99
iterator.each { |*args|
-
99
method_name = args.shift
-
99
ensure_method_not_already_defined(method_name)
-
99
expectation = Expectation.new(self, method_name, backtrace)
-
99
expectation.returns(args.shift) if args.length > 0
-
99
@expectations.add(expectation)
-
}
-
end
-
-
# Adds an expectation that the specified method may be called any number of times with any parameters.
-
#
-
# @param [Symbol,String] method_name name of stubbed method
-
# @param [Hash] stubbed_methods_vs_return_values stubbed method name symbols as keys and corresponding return values as values - these stubbed methods are setup as if {#stubs} were called multiple times.
-
#
-
# @overload def stubs(method_name)
-
# @overload def stubs(stubbed_methods_vs_return_values)
-
# @return [Expectation] last-built expectation which can be further modified by methods on {Expectation}.
-
#
-
# @example No error raised however many times stubbed method is invoked
-
# object = mock()
-
# object.stubs(:stubbed_method)
-
# object.stubbed_method
-
# object.stubbed_method
-
# # no error raised
-
#
-
# @example Setup multiple expectations using +stubbed_methods_vs_return_values+.
-
# object = mock()
-
# object.stubs(:stubbed_method_one => :result_one, :stubbed_method_two => :result_two)
-
#
-
# # is exactly equivalent to
-
#
-
# object = mock()
-
# object.stubs(:stubbed_method_one).returns(:result_one)
-
# object.stubs(:stubbed_method_two).returns(:result_two)
-
1
def stubs(method_name_or_hash, backtrace = nil)
-
1539
iterator = ArgumentIterator.new(method_name_or_hash)
-
1539
iterator.each { |*args|
-
2164
method_name = args.shift
-
2164
ensure_method_not_already_defined(method_name)
-
2164
expectation = Expectation.new(self, method_name, backtrace)
-
2164
expectation.at_least(0)
-
2164
expectation.returns(args.shift) if args.length > 0
-
2164
@expectations.add(expectation)
-
}
-
end
-
-
# Removes the specified stubbed method (added by calls to {#expects} or {#stubs}) and all expectations associated with it.
-
#
-
# @param [Symbol] method_name name of method to unstub.
-
#
-
# @example Invoking an unstubbed method causes error to be raised
-
# object = mock('mock') do
-
# object.stubs(:stubbed_method).returns(:result_one)
-
# object.stubbed_method # => :result_one
-
# object.unstub(:stubbed_method)
-
# object.stubbed_method # => unexpected invocation: #<Mock:mock>.stubbed_method()
-
1
def unstub(method_name)
-
463
@expectations.remove_all_matching_method(method_name)
-
end
-
-
# Constrains the {Mock} instance so that it can only expect or stub methods to which +responder+ responds. The constraint is only applied at method invocation time.
-
#
-
# A +NoMethodError+ will be raised if the +responder+ does not +#respond_to?+ a method invocation (even if the method has been expected or stubbed).
-
#
-
# The {Mock} instance will delegate its +#respond_to?+ method to the +responder+.
-
#
-
# @param [Object, #respond_to?] responder an object used to determine whether {Mock} instance should +#respond_to?+ to an invocation.
-
# @return [Mock] the same {Mock} instance, thereby allowing invocations of other {Mock} methods to be chained.
-
#
-
# @example Normal mocking
-
# sheep = mock('sheep')
-
# sheep.expects(:chew)
-
# sheep.expects(:foo)
-
# sheep.respond_to?(:chew) # => true
-
# sheep.respond_to?(:foo) # => true
-
# sheep.chew
-
# sheep.foo
-
# # no error raised
-
#
-
# @example Using {#responds_like} with an instance method
-
# class Sheep
-
# def chew(grass); end
-
# end
-
#
-
# sheep = mock('sheep')
-
# sheep.responds_like(Sheep.new)
-
# sheep.expects(:chew)
-
# sheep.expects(:foo)
-
# sheep.respond_to?(:chew) # => true
-
# sheep.respond_to?(:foo) # => false
-
# sheep.chew
-
# sheep.foo # => raises NoMethodError exception
-
#
-
# @example Using {#responds_like} with a class method
-
# class Sheep
-
# def self.number_of_legs; end
-
# end
-
#
-
# sheep_class = mock('sheep_class')
-
# sheep_class.responds_like(Sheep)
-
# sheep_class.stubs(:number_of_legs).returns(4)
-
# sheep_class.expects(:foo)
-
# sheep_class.respond_to?(:number_of_legs) # => true
-
# sheep_class.respond_to?(:foo) # => false
-
# assert_equal 4, sheep_class.number_of_legs
-
# sheep_class.foo # => raises NoMethodError exception
-
1
def responds_like(responder)
-
@responder = responder
-
self
-
end
-
-
# @private
-
1
def initialize(mockery, name = nil, &block)
-
948
@mockery = mockery
-
948
@name = name || DefaultName.new(self)
-
948
@expectations = ExpectationList.new
-
948
@everything_stubbed = false
-
948
@responder = nil
-
948
instance_eval(&block) if block
-
end
-
-
# @private
-
1
attr_reader :everything_stubbed
-
-
1
alias_method :__expects__, :expects
-
-
1
alias_method :__stubs__, :stubs
-
-
1
alias_method :quacks_like, :responds_like
-
-
# @private
-
1
def __expectations__
-
948
@expectations
-
end
-
-
# @private
-
1
def stub_everything
-
@everything_stubbed = true
-
end
-
-
# @private
-
1
def method_missing(symbol, *arguments, &block)
-
584
if @responder and not @responder.respond_to?(symbol)
-
raise NoMethodError, "undefined method `#{symbol}' for #{self.mocha_inspect} which responds like #{@responder.mocha_inspect}"
-
end
-
584
if matching_expectation_allowing_invocation = @expectations.match_allowing_invocation(symbol, *arguments)
-
584
matching_expectation_allowing_invocation.invoke(&block)
-
else
-
if (matching_expectation = @expectations.match(symbol, *arguments)) || (!matching_expectation && !@everything_stubbed)
-
matching_expectation.invoke(&block) if matching_expectation
-
message = UnexpectedInvocation.new(self, symbol, *arguments).to_s
-
message << @mockery.mocha_inspect
-
raise ExpectationErrorFactory.build(message, caller)
-
end
-
end
-
end
-
-
# @private
-
1
def respond_to?(symbol, include_private = false)
-
4
if @responder then
-
if @responder.method(:respond_to?).arity > 1
-
@responder.respond_to?(symbol, include_private)
-
else
-
@responder.respond_to?(symbol)
-
end
-
else
-
4
@everything_stubbed || @expectations.matches_method?(symbol)
-
end
-
end
-
-
# @private
-
1
def __verified__?(assertion_counter = nil)
-
948
@expectations.verified?(assertion_counter)
-
end
-
-
# @private
-
1
def mocha_inspect
-
@name.mocha_inspect
-
end
-
-
# @private
-
1
def inspect
-
mocha_inspect
-
end
-
-
# @private
-
1
def ensure_method_not_already_defined(method_name)
-
2263
self.__metaclass__.send(:undef_method, method_name) if self.__metaclass__.method_defined?(method_name)
-
end
-
-
# @private
-
1
def any_expectations?
-
463
@expectations.any?
-
end
-
-
end
-
-
end
-
1
require 'mocha/central'
-
1
require 'mocha/mock'
-
1
require 'mocha/names'
-
1
require 'mocha/state_machine'
-
1
require 'mocha/logger'
-
1
require 'mocha/configuration'
-
1
require 'mocha/stubbing_error'
-
1
require 'mocha/expectation_error_factory'
-
-
1
module Mocha
-
-
1
class Mockery
-
-
1
class << self
-
-
1
def instance
-
9839
@instance ||= new
-
end
-
-
1
def reset_instance
-
3948
@instance = nil
-
end
-
-
end
-
-
1
def named_mock(name, &block)
-
add_mock(Mock.new(self, Name.new(name), &block))
-
end
-
-
1
def unnamed_mock(&block)
-
638
add_mock(Mock.new(self, &block))
-
end
-
-
1
def mock_impersonating(object, &block)
-
291
add_mock(Mock.new(self, ImpersonatingName.new(object), &block))
-
end
-
-
1
def mock_impersonating_any_instance_of(klass, &block)
-
19
add_mock(Mock.new(self, ImpersonatingAnyInstanceName.new(klass), &block))
-
end
-
-
1
def new_state_machine(name)
-
add_state_machine(StateMachine.new(name))
-
end
-
-
1
def verify(assertion_counter = nil)
-
4896
unless mocks.all? { |mock| mock.__verified__?(assertion_counter) }
-
message = "not all expectations were satisfied\n#{mocha_inspect}"
-
if unsatisfied_expectations.empty?
-
backtrace = caller
-
else
-
backtrace = unsatisfied_expectations[0].backtrace
-
end
-
raise ExpectationErrorFactory.build(message, backtrace)
-
end
-
3948
expectations.each do |e|
-
2263
unless Mocha::Configuration.allow?(:stubbing_method_unnecessarily)
-
unless e.used?
-
on_stubbing_method_unnecessarily(e)
-
end
-
end
-
end
-
end
-
-
1
def teardown
-
3948
stubba.unstub_all
-
3948
reset
-
end
-
-
1
def stubba
-
4943
@stubba ||= Central.new
-
end
-
-
1
def mocks
-
8844
@mocks ||= []
-
end
-
-
1
def state_machines
-
@state_machines ||= []
-
end
-
-
1
def mocha_inspect
-
message = ""
-
message << "unsatisfied expectations:\n- #{unsatisfied_expectations.map { |e| e.mocha_inspect }.join("\n- ")}\n" unless unsatisfied_expectations.empty?
-
message << "satisfied expectations:\n- #{satisfied_expectations.map { |e| e.mocha_inspect }.join("\n- ")}\n" unless satisfied_expectations.empty?
-
message << "states:\n- #{state_machines.map { |sm| sm.mocha_inspect }.join("\n- ")}" unless state_machines.empty?
-
message
-
end
-
-
1
def on_stubbing(object, method)
-
995
method = RUBY_VERSION < '1.9' ? method.to_s : method.to_sym
-
995
unless Mocha::Configuration.allow?(:stubbing_non_existent_method)
-
unless object.method_exists?(method, include_public_methods = true)
-
on_stubbing_non_existent_method(object, method)
-
end
-
end
-
995
unless Mocha::Configuration.allow?(:stubbing_non_public_method)
-
if object.method_exists?(method, include_public_methods = false)
-
on_stubbing_non_public_method(object, method)
-
end
-
end
-
995
unless Mocha::Configuration.allow?(:stubbing_method_on_nil)
-
995
if object.nil?
-
on_stubbing_method_on_nil(object, method)
-
end
-
end
-
995
unless Mocha::Configuration.allow?(:stubbing_method_on_non_mock_object)
-
on_stubbing_method_on_non_mock_object(object, method)
-
end
-
end
-
-
1
def on_stubbing_non_existent_method(object, method)
-
if Mocha::Configuration.prevent?(:stubbing_non_existent_method)
-
raise StubbingError.new("stubbing non-existent method: #{object.mocha_inspect}.#{method}", caller)
-
end
-
if Mocha::Configuration.warn_when?(:stubbing_non_existent_method)
-
logger.warn "stubbing non-existent method: #{object.mocha_inspect}.#{method}"
-
end
-
end
-
-
1
def on_stubbing_non_public_method(object, method)
-
if Mocha::Configuration.prevent?(:stubbing_non_public_method)
-
raise StubbingError.new("stubbing non-public method: #{object.mocha_inspect}.#{method}", caller)
-
end
-
if Mocha::Configuration.warn_when?(:stubbing_non_public_method)
-
logger.warn "stubbing non-public method: #{object.mocha_inspect}.#{method}"
-
end
-
end
-
-
1
def on_stubbing_method_on_nil(object, method)
-
if Mocha::Configuration.prevent?(:stubbing_method_on_nil)
-
raise StubbingError.new("stubbing method on nil: #{object.mocha_inspect}.#{method}", caller)
-
end
-
if Mocha::Configuration.warn_when?(:stubbing_method_on_nil)
-
logger.warn "stubbing method on nil: #{object.mocha_inspect}.#{method}"
-
end
-
end
-
-
1
def on_stubbing_method_on_non_mock_object(object, method)
-
if Mocha::Configuration.prevent?(:stubbing_method_on_non_mock_object)
-
raise StubbingError.new("stubbing method on non-mock object: #{object.mocha_inspect}.#{method}", caller)
-
end
-
if Mocha::Configuration.warn_when?(:stubbing_method_on_non_mock_object)
-
logger.warn "stubbing method on non-mock object: #{object.mocha_inspect}.#{method}"
-
end
-
end
-
-
1
def on_stubbing_method_unnecessarily(expectation)
-
if Mocha::Configuration.prevent?(:stubbing_method_unnecessarily)
-
raise StubbingError.new("stubbing method unnecessarily: #{expectation.method_signature}", expectation.backtrace)
-
end
-
if Mocha::Configuration.warn_when?(:stubbing_method_unnecessarily)
-
logger.warn "stubbing method unnecessarily: #{expectation.method_signature}"
-
end
-
end
-
-
1
attr_writer :logger
-
-
1
def logger
-
@logger ||= Logger.new($stderr)
-
end
-
-
-
1
private
-
-
1
def expectations
-
4896
mocks.map { |mock| mock.__expectations__.to_a }.flatten
-
end
-
-
1
def unsatisfied_expectations
-
expectations.reject { |e| e.verified? }
-
end
-
-
1
def satisfied_expectations
-
expectations.select { |e| e.verified? }
-
end
-
-
1
def add_mock(mock)
-
948
mocks << mock
-
948
mock
-
end
-
-
1
def add_state_machine(state_machine)
-
state_machines << state_machine
-
state_machine
-
end
-
-
1
def reset
-
3948
@stubba = nil
-
3948
@mocks = nil
-
3948
@state_machines = nil
-
end
-
-
end
-
-
end
-
1
require 'mocha/class_method'
-
-
1
module Mocha
-
-
1
class ModuleMethod < ClassMethod
-
-
1
def method_exists?(method)
-
107
return true if stubbee.public_methods(false).include?(method)
-
23
return true if stubbee.protected_methods(false).include?(method)
-
23
return true if stubbee.private_methods(false).include?(method)
-
23
return false
-
end
-
-
end
-
-
end
-
1
require 'mocha/module_method'
-
-
1
module Mocha
-
-
# @private
-
1
module ModuleMethods
-
-
1
def stubba_method
-
137
Mocha::ModuleMethod
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class MultipleYields
-
-
1
attr_reader :parameter_groups
-
-
1
def initialize(*parameter_groups)
-
@parameter_groups = parameter_groups
-
end
-
-
1
def each
-
@parameter_groups.each do |parameter_group|
-
yield(parameter_group)
-
end
-
end
-
-
end
-
-
end
-
-
1
module Mocha
-
-
1
class ImpersonatingName
-
-
1
def initialize(object)
-
291
@object = object
-
end
-
-
1
def mocha_inspect
-
@object.mocha_inspect
-
end
-
-
end
-
-
1
class ImpersonatingAnyInstanceName
-
-
1
def initialize(klass)
-
19
@klass = klass
-
end
-
-
1
def mocha_inspect
-
"#<AnyInstance:#{@klass.mocha_inspect}>"
-
end
-
-
end
-
-
1
class Name
-
-
1
def initialize(name)
-
@name = name
-
end
-
-
1
def mocha_inspect
-
"#<Mock:#{@name}>"
-
end
-
-
end
-
-
1
class DefaultName
-
-
1
def initialize(mock)
-
638
@mock = mock
-
end
-
-
1
def mocha_inspect
-
address = @mock.__id__ * 2
-
address += 0x100000000 if address < 0
-
"#<Mock:0x#{'%x' % address}>"
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class NoYields
-
-
1
def each
-
end
-
-
end
-
-
end
-
-
1
require 'mocha/mockery'
-
1
require 'mocha/instance_method'
-
1
require 'mocha/argument_iterator'
-
1
require 'mocha/expectation_error_factory'
-
-
1
module Mocha
-
-
# Methods added to all objects to allow mocking and stubbing on real (i.e. non-mock) objects.
-
#
-
# Both {#expects} and {#stubs} return an {Expectation} which can be further modified by methods on {Expectation}.
-
1
module ObjectMethods
-
-
# @private
-
1
alias_method :_method, :method
-
-
# @private
-
1
def mocha
-
2166
@mocha ||= Mocha::Mockery.instance.mock_impersonating(self)
-
end
-
-
# @private
-
1
def reset_mocha
-
310
@mocha = nil
-
end
-
-
# @private
-
1
def stubba_method
-
71
Mocha::InstanceMethod
-
end
-
-
# @private
-
1
def stubba_object
-
974
self
-
end
-
-
# Adds an expectation that the specified method must be called exactly once with any parameters.
-
#
-
# The original implementation of the method is replaced during the test and then restored at the end of the test.
-
#
-
# @param [Symbol,String] method_name name of expected method
-
# @param [Hash] expected_methods_vs_return_values expected method name symbols as keys and corresponding return values as values - these expectations are setup as if {#expects} were called multiple times.
-
#
-
# @overload def expects(method_name)
-
# @overload def expects(expected_methods_vs_return_values)
-
# @return [Expectation] last-built expectation which can be further modified by methods on {Expectation}.
-
# @raise [StubbingError] if attempting to stub method which is not allowed.
-
#
-
# @example Setting up an expectation on a non-mock object.
-
# product = Product.new
-
# product.expects(:save).returns(true)
-
# assert_equal true, product.save
-
#
-
# @example Setting up multiple expectations on a non-mock object.
-
# product = Product.new
-
# product.expects(:valid? => true, :save => true)
-
#
-
# # exactly equivalent to
-
#
-
# product = Product.new
-
# product.expects(:valid?).returns(true)
-
# product.expects(:save).returns(true)
-
#
-
# @see Mock#expects
-
1
def expects(expected_methods_vs_return_values)
-
97
if expected_methods_vs_return_values.to_s =~ /the[^a-z]*spanish[^a-z]*inquisition/i
-
raise ExpectationErrorFactory.build('NOBODY EXPECTS THE SPANISH INQUISITION!')
-
end
-
97
if frozen?
-
raise StubbingError.new("can't stub method on frozen object: #{mocha_inspect}", caller)
-
end
-
97
expectation = nil
-
97
mockery = Mocha::Mockery.instance
-
97
iterator = ArgumentIterator.new(expected_methods_vs_return_values)
-
97
iterator.each { |*args|
-
97
method_name = args.shift
-
97
mockery.on_stubbing(self, method_name)
-
97
method = stubba_method.new(stubba_object, method_name)
-
97
mockery.stubba.stub(method)
-
97
expectation = mocha.expects(method_name, caller)
-
97
expectation.returns(args.shift) if args.length > 0
-
}
-
97
expectation
-
end
-
-
# Adds an expectation that the specified method may be called any number of times with any parameters.
-
#
-
# @param [Symbol,String] method_name name of stubbed method
-
# @param [Hash] stubbed_methods_vs_return_values stubbed method name symbols as keys and corresponding return values as values - these stubbed methods are setup as if {#stubs} were called multiple times.
-
#
-
# @overload def stubs(method_name)
-
# @overload def stubs(stubbed_methods_vs_return_values)
-
# @return [Expectation] last-built expectation which can be further modified by methods on {Expectation}.
-
# @raise [StubbingError] if attempting to stub method which is not allowed.
-
#
-
# @example Setting up a stubbed methods on a non-mock object.
-
# product = Product.new
-
# product.stubs(:save).returns(true)
-
# assert_equal true, product.save
-
#
-
# @example Setting up multiple stubbed methods on a non-mock object.
-
# product = Product.new
-
# product.stubs(:valid? => true, :save => true)
-
#
-
# # exactly equivalent to
-
#
-
# product = Product.new
-
# product.stubs(:valid?).returns(true)
-
# product.stubs(:save).returns(true)
-
#
-
# @see Mock#stubs
-
1
def stubs(stubbed_methods_vs_return_values)
-
898
if frozen?
-
raise StubbingError.new("can't stub method on frozen object: #{mocha_inspect}", caller)
-
end
-
898
expectation = nil
-
898
mockery = Mocha::Mockery.instance
-
898
iterator = ArgumentIterator.new(stubbed_methods_vs_return_values)
-
898
iterator.each { |*args|
-
898
method_name = args.shift
-
898
mockery.on_stubbing(self, method_name)
-
898
method = stubba_method.new(stubba_object, method_name)
-
898
mockery.stubba.stub(method)
-
898
expectation = mocha.stubs(method_name, caller)
-
898
expectation.returns(args.shift) if args.length > 0
-
}
-
898
expectation
-
end
-
-
# Removes the specified stubbed methods (added by calls to {#expects} or {#stubs}) and all expectations associated with them.
-
#
-
# Restores the original behaviour of the methods before they were stubbed.
-
#
-
# WARNING: If you {#unstub} a method which still has unsatisfied expectations, you may be removing the only way those expectations can be satisfied. Use {#unstub} with care.
-
#
-
# @param [Array<Symbol>] method_names names of methods to unstub.
-
#
-
# @example Stubbing and unstubbing a method on a real (non-mock) object.
-
# multiplier = Multiplier.new
-
# multiplier.double(2) # => 4
-
# multiplier.stubs(:double).raises # new behaviour defined
-
# multiplier.double(2) # => raises exception
-
# multiplier.unstub(:double) # original behaviour restored
-
# multiplier.double(2) # => 4
-
#
-
# @example Unstubbing multiple methods on a real (non-mock) object.
-
# multiplier.unstub(:double, :triple)
-
#
-
# # exactly equivalent to
-
#
-
# multiplier.unstub(:double)
-
# multiplier.unstub(:triple)
-
1
def unstub(*method_names)
-
mockery = Mocha::Mockery.instance
-
method_names.each do |method_name|
-
method = stubba_method.new(stubba_object, method_name)
-
mockery.stubba.unstub(method)
-
end
-
end
-
-
# @private
-
1
def method_exists?(method, include_public_methods = true)
-
if include_public_methods
-
return true if public_methods(include_superclass_methods = true).include?(method)
-
return true if respond_to?(method.to_sym)
-
end
-
return true if protected_methods(include_superclass_methods = true).include?(method)
-
return true if private_methods(include_superclass_methods = true).include?(method)
-
return false
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
# Used as parameters for {Expectation#with} to restrict the parameter values which will match the expectation. Can be nested.
-
1
module ParameterMatchers; end
-
-
end
-
-
1
require 'mocha/parameter_matchers/object'
-
-
1
require 'mocha/parameter_matchers/all_of'
-
1
require 'mocha/parameter_matchers/any_of'
-
1
require 'mocha/parameter_matchers/any_parameters'
-
1
require 'mocha/parameter_matchers/anything'
-
1
require 'mocha/parameter_matchers/equals'
-
1
require 'mocha/parameter_matchers/has_entry'
-
1
require 'mocha/parameter_matchers/has_entries'
-
1
require 'mocha/parameter_matchers/has_key'
-
1
require 'mocha/parameter_matchers/has_value'
-
1
require 'mocha/parameter_matchers/includes'
-
1
require 'mocha/parameter_matchers/instance_of'
-
1
require 'mocha/parameter_matchers/is_a'
-
1
require 'mocha/parameter_matchers/kind_of'
-
1
require 'mocha/parameter_matchers/not'
-
1
require 'mocha/parameter_matchers/optionally'
-
1
require 'mocha/parameter_matchers/regexp_matches'
-
1
require 'mocha/parameter_matchers/responds_with'
-
1
require 'mocha/parameter_matchers/yaml_equivalent'
-
1
require 'mocha/parameter_matchers/query_string'
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches if all +matchers+ match.
-
#
-
# @param [*Array<Base>] parameter_matchers parameter matchers.
-
# @return [AllOf] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example All parameter matchers match.
-
# object = mock()
-
# object.expects(:method_1).with(all_of(includes(1), includes(3)))
-
# object.method_1([1, 3])
-
# # no error raised
-
#
-
# @example One of the parameter matchers does not match.
-
# object = mock()
-
# object.expects(:method_1).with(all_of(includes(1), includes(3)))
-
# object.method_1([1, 2])
-
# # error raised, because method_1 was not called with object including 1 and 3
-
1
def all_of(*matchers)
-
AllOf.new(*matchers)
-
end
-
-
# Parameter matcher which combines a number of other matchers using a logical AND.
-
1
class AllOf < Base
-
-
# @private
-
1
def initialize(*matchers)
-
@matchers = matchers
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
@matchers.all? { |matcher| matcher.to_matcher.matches?([parameter]) }
-
end
-
-
# @private
-
1
def mocha_inspect
-
"all_of(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches if any +matchers+ match.
-
#
-
# @param [*Array<Base>] parameter_matchers parameter matchers.
-
# @return [AnyOf] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example One parameter matcher matches.
-
# object = mock()
-
# object.expects(:method_1).with(any_of(1, 3))
-
# object.method_1(1)
-
# # no error raised
-
#
-
# @example The other parameter matcher matches.
-
# object = mock()
-
# object.expects(:method_1).with(any_of(1, 3))
-
# object.method_1(3)
-
# # no error raised
-
#
-
# @example Neither parameter matcher matches.
-
# object = mock()
-
# object.expects(:method_1).with(any_of(1, 3))
-
# object.method_1(2)
-
# # error raised, because method_1 was not called with 1 or 3
-
1
def any_of(*matchers)
-
AnyOf.new(*matchers)
-
end
-
-
# Parameter matcher which combines a number of other matchers using a logical OR.
-
1
class AnyOf < Base
-
-
# @private
-
1
def initialize(*matchers)
-
@matchers = matchers
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
@matchers.any? { |matcher| matcher.to_matcher.matches?([parameter]) }
-
end
-
-
# @private
-
1
def mocha_inspect
-
"any_of(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any parameters. This is used as the default for a newly built expectation.
-
#
-
# @return [AnyParameters] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Any parameters will match.
-
# object = mock()
-
# object.expects(:method_1).with(any_parameters)
-
# object.method_1(1, 2, 3, 4)
-
# # no error raised
-
#
-
# object = mock()
-
# object.expects(:method_1).with(any_parameters)
-
# object.method_1(5, 6, 7, 8, 9, 0)
-
# # no error raised
-
1
def any_parameters
-
AnyParameters.new
-
end
-
-
# Parameter matcher which always matches whatever the parameters.
-
1
class AnyParameters < Base
-
-
# @private
-
1
def matches?(available_parameters)
-
516
while available_parameters.length > 0 do
-
92
available_parameters.shift
-
end
-
516
return true
-
end
-
-
# @private
-
1
def mocha_inspect
-
"any_parameters"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any object.
-
#
-
# @return [Anything] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Any object will match.
-
# object = mock()
-
# object.expects(:method_1).with(anything)
-
# object.method_1('foo')
-
# object.method_1(789)
-
# object.method_1(:bar)
-
# # no error raised
-
1
def anything
-
Anything.new
-
end
-
-
# Parameter matcher which always matches a single parameter.
-
1
class Anything < Base
-
-
# @private
-
1
def matches?(available_parameters)
-
available_parameters.shift
-
return true
-
end
-
-
# @private
-
1
def mocha_inspect
-
"anything"
-
end
-
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# @abstract Subclass and implement +#matches?+ and +#mocha_inspect+ to define a custom matcher. Also add a suitably named instance method to {ParameterMatchers} to build an instance of the new matcher c.f. {#equals}.
-
1
class Base
-
-
# @private
-
1
def to_matcher
-
518
self
-
end
-
-
# A shorthand way of combining two matchers when both must match.
-
#
-
# Returns a new {AllOf} parameter matcher combining two matchers using a logical AND.
-
#
-
# This shorthand will not work with an implicit equals match. Instead, an explicit {Equals} matcher should be used.
-
#
-
# @param [Base] matcher parameter matcher.
-
# @return [AllOf] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Alternative ways to combine matchers with a logical AND.
-
# object = mock()
-
# object.expects(:run).with(all_of(has_key(:foo), has_key(:bar)))
-
# object.run(:foo => 'foovalue', :bar => 'barvalue')
-
#
-
# # is exactly equivalent to
-
#
-
# object.expects(:run).with(has_key(:foo) & has_key(:bar))
-
# object.run(:foo => 'foovalue', :bar => 'barvalue)
-
1
def &(matcher)
-
AllOf.new(self, matcher)
-
end
-
-
# A shorthand way of combining two matchers when at least one must match.
-
#
-
# Returns a new +AnyOf+ parameter matcher combining two matchers using a logical OR.
-
#
-
# This shorthand will not work with an implicit equals match. Instead, an explicit {Equals} matcher should be used.
-
#
-
# @param [Base] matcher parameter matcher.
-
# @return [AnyOf] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Alternative ways to combine matchers with a logical OR.
-
# object = mock()
-
# object.expects(:run).with(any_of(has_key(:foo), has_key(:bar)))
-
# object.run(:foo => 'foovalue')
-
#
-
# # is exactly equivalent to
-
#
-
# object.expects(:run).with(has_key(:foo) | has_key(:bar))
-
# object.run(:foo => 'foovalue')
-
#
-
# @example Using an explicit {Equals} matcher in combination with {#|}.
-
# object.expects(:run).with(equals(1) | equals(2))
-
# object.run(1) # passes
-
# object.run(2) # passes
-
# object.run(3) # fails
-
1
def |(matcher)
-
AnyOf.new(self, matcher)
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any +Object+ equalling +value+.
-
#
-
# @param [Object] value expected value.
-
# @return [Equals] parameter matcher.
-
#
-
# @see Expectation#with
-
# @see Object#==
-
#
-
# @example Actual parameter equals expected parameter.
-
# object = mock()
-
# object.expects(:method_1).with(equals(2))
-
# object.method_1(2)
-
# # no error raised
-
#
-
# @example Actual parameter does not equal expected parameter.
-
# object = mock()
-
# object.expects(:method_1).with(equals(2))
-
# object.method_1(3)
-
# # error raised, because method_1 was not called with an +Object+ that equals 3
-
1
def equals(value)
-
Equals.new(value)
-
end
-
-
# Parameter matcher which matches when actual parameter equals expected value.
-
1
class Equals < Base
-
-
# @private
-
1
def initialize(value)
-
576
@value = value
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
402
parameter = available_parameters.shift
-
402
parameter == @value
-
end
-
-
# @private
-
1
def mocha_inspect
-
@value.mocha_inspect
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
1
require 'mocha/parameter_matchers/all_of'
-
1
require 'mocha/parameter_matchers/has_entry'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches +Hash+ containing all +entries+.
-
#
-
# @param [Hash] entries expected +Hash+ entries.
-
# @return [HasEntries] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual parameter contains all expected entries.
-
# object = mock()
-
# object.expects(:method_1).with(has_entries('key_1' => 1, 'key_2' => 2))
-
# object.method_1('key_1' => 1, 'key_2' => 2, 'key_3' => 3)
-
# # no error raised
-
#
-
# @example Actual parameter does not contain all expected entries.
-
# object = mock()
-
# object.expects(:method_1).with(has_entries('key_1' => 1, 'key_2' => 2))
-
# object.method_1('key_1' => 1, 'key_2' => 99)
-
# # error raised, because method_1 was not called with Hash containing entries: 'key_1' => 1, 'key_2' => 2
-
1
def has_entries(entries)
-
HasEntries.new(entries)
-
end
-
-
# Parameter matcher which matches when actual parameter contains all expected +Hash+ entries.
-
1
class HasEntries < Base
-
-
# @private
-
1
def initialize(entries)
-
@entries = entries
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
has_entry_matchers = @entries.map { |key, value| HasEntry.new(key, value) }
-
AllOf.new(*has_entry_matchers).matches?([parameter])
-
end
-
-
# @private
-
1
def mocha_inspect
-
"has_entries(#{@entries.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches +Hash+ containing entry with +key+ and +value+.
-
#
-
# @overload def has_entry(key, value)
-
# @param [Object] key key for entry.
-
# @param [Object] value value for entry.
-
# @overload def has_entry(single_entry_hash)
-
# @param [Hash] single_entry_hash +Hash+ with single entry.
-
# @raise [ArgumentError] if +single_entry_hash+ does not contain exactly one entry.
-
#
-
# @return [HasEntry] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual parameter contains expected entry supplied as key and value.
-
# object = mock()
-
# object.expects(:method_1).with(has_entry('key_1', 1))
-
# object.method_1('key_1' => 1, 'key_2' => 2)
-
# # no error raised
-
#
-
# @example Actual parameter contains expected entry supplied as +Hash+ entry.
-
# object = mock()
-
# object.expects(:method_1).with(has_entry('key_1' => 1))
-
# object.method_1('key_1' => 1, 'key_2' => 2)
-
# # no error raised
-
#
-
# @example Actual parameter does not contain expected entry supplied as key and value.
-
# object = mock()
-
# object.expects(:method_1).with(has_entry('key_1', 1))
-
# object.method_1('key_1' => 2, 'key_2' => 1)
-
# # error raised, because method_1 was not called with Hash containing entry: 'key_1' => 1
-
#
-
# @example Actual parameter does not contain expected entry supplied as +Hash+ entry.
-
#
-
# object = mock()
-
# object.expects(:method_1).with(has_entry('key_1' => 1))
-
# object.method_1('key_1' => 2, 'key_2' => 1)
-
# # error raised, because method_1 was not called with Hash containing entry: 'key_1' => 1
-
1
def has_entry(*options)
-
key, value = options.shift, options.shift
-
if key.is_a?(Hash)
-
case key.length
-
when 0
-
raise ArgumentError.new("Argument has no entries.")
-
when 1
-
key, value = key.to_a.flatten
-
else
-
raise ArgumentError.new("Argument has multiple entries. Use Mocha::ParameterMatchers#has_entries instead.")
-
end
-
end
-
HasEntry.new(key, value)
-
end
-
-
# Parameter matcher which matches when actual parameter contains expected +Hash+ entry.
-
1
class HasEntry < Base
-
-
# @private
-
1
def initialize(key, value)
-
@key, @value = key, value
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
return false unless parameter.respond_to?(:keys) && parameter.respond_to?(:[])
-
matching_keys = parameter.keys.select { |key| @key.to_matcher.matches?([key]) }
-
matching_keys.any? { |key| @value.to_matcher.matches?([parameter[key]]) }
-
end
-
-
# @private
-
1
def mocha_inspect
-
"has_entry(#{@key.mocha_inspect} => #{@value.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches +Hash+ containing +key+.
-
#
-
# @param [Object] key expected key.
-
# @return [HasKey] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual parameter contains entry with expected key.
-
# object = mock()
-
# object.expects(:method_1).with(has_key('key_1'))
-
# object.method_1('key_1' => 1, 'key_2' => 2)
-
# # no error raised
-
#
-
# @example Actual parameter does not contain entry with expected key.
-
# object = mock()
-
# object.expects(:method_1).with(has_key('key_1'))
-
# object.method_1('key_2' => 2)
-
# # error raised, because method_1 was not called with Hash containing key: 'key_1'
-
1
def has_key(key)
-
HasKey.new(key)
-
end
-
-
# Parameter matcher which matches when actual parameter contains +Hash+ entry with expected key.
-
1
class HasKey < Base
-
-
# @private
-
1
def initialize(key)
-
@key = key
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
return false unless parameter.respond_to?(:keys)
-
parameter.keys.any? { |key| @key.to_matcher.matches?([key]) }
-
end
-
-
# @private
-
1
def mocha_inspect
-
"has_key(#{@key.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches +Hash+ containing +value+.
-
#
-
# @param [Object] value expected value.
-
# @return [HasValue] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual parameter contains entry with expected value.
-
# object = mock()
-
# object.expects(:method_1).with(has_value(1))
-
# object.method_1('key_1' => 1, 'key_2' => 2)
-
# # no error raised
-
#
-
# @example Actual parameter does not contain entry with expected value.
-
# object = mock()
-
# object.expects(:method_1).with(has_value(1))
-
# object.method_1('key_2' => 2)
-
# # error raised, because method_1 was not called with Hash containing value: 1
-
1
def has_value(value)
-
HasValue.new(value)
-
end
-
-
# Parameter matcher which matches when actual parameter contains +Hash+ entry with expected value.
-
1
class HasValue < Base
-
-
# @private
-
1
def initialize(value)
-
@value = value
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
return false unless parameter.respond_to?(:values)
-
parameter.values.any? { |value| @value.to_matcher.matches?([value]) }
-
end
-
-
# @private
-
1
def mocha_inspect
-
"has_value(#{@value.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any object that responds with +true+ to +include?(item)+.
-
#
-
# @param [Object] item expected item.
-
# @return [Includes] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual parameter includes item.
-
# object = mock()
-
# object.expects(:method_1).with(includes('foo'))
-
# object.method_1(['foo', 'bar'])
-
# # no error raised
-
#
-
# @example Actual parameter does not include item.
-
# object.method_1(['baz'])
-
# # error raised, because ['baz'] does not include 'foo'.
-
1
def includes(item)
-
Includes.new(item)
-
end
-
-
# Parameter matcher which matches when actual parameter includes expected value.
-
1
class Includes < Base
-
-
# @private
-
1
def initialize(item)
-
@item = item
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
return false unless parameter.respond_to?(:include?)
-
return parameter.include?(@item)
-
end
-
-
# @private
-
1
def mocha_inspect
-
"includes(#{@item.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any object that is an instance of +klass+
-
#
-
# @param [Class] klass expected class.
-
# @return [InstanceOf] parameter matcher.
-
#
-
# @see Expectation#with
-
# @see Kernel#instance_of?
-
#
-
# @example Actual parameter is an instance of +String+.
-
# object = mock()
-
# object.expects(:method_1).with(instance_of(String))
-
# object.method_1('string')
-
# # no error raised
-
#
-
# @example Actual parameter is not an instance of +String+.
-
# object = mock()
-
# object.expects(:method_1).with(instance_of(String))
-
# object.method_1(99)
-
# # error raised, because method_1 was not called with an instance of String
-
1
def instance_of(klass)
-
2
InstanceOf.new(klass)
-
end
-
-
# Parameter matcher which matches when actual parameter is an instance of the specified class.
-
1
class InstanceOf < Base
-
-
# @private
-
1
def initialize(klass)
-
2
@klass = klass
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
2
parameter = available_parameters.shift
-
2
parameter.instance_of?(@klass)
-
end
-
-
# @private
-
1
def mocha_inspect
-
"instance_of(#{@klass.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any object that is a +klass+.
-
#
-
# @param [Class] klass expected class.
-
# @return [IsA] parameter matcher.
-
#
-
# @see Expectation#with
-
# @see Kernel#is_a?
-
#
-
# @example Actual parameter is a +Integer+.
-
# object = mock()
-
# object.expects(:method_1).with(is_a(Integer))
-
# object.method_1(99)
-
# # no error raised
-
#
-
# @example Actual parameter is not a +Integer+.
-
# object = mock()
-
# object.expects(:method_1).with(is_a(Integer))
-
# object.method_1('string')
-
# # error raised, because method_1 was not called with an Integer
-
1
def is_a(klass)
-
IsA.new(klass)
-
end
-
-
# Parameter matcher which matches when actual parameter is a specific class.
-
1
class IsA < Base
-
-
# @private
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
parameter.is_a?(@klass)
-
end
-
-
# @private
-
1
def mocha_inspect
-
"is_a(#{@klass.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any +Object+ that is a kind of +klass+.
-
#
-
# @param [Class] klass expected class.
-
# @return [KindOf] parameter matcher.
-
#
-
# @see Expectation#with
-
# @see Kernel#kind_of?
-
#
-
# @example Actual parameter is a kind of +Integer+.
-
# object = mock()
-
# object.expects(:method_1).with(kind_of(Integer))
-
# object.method_1(99)
-
# # no error raised
-
#
-
# @example Actual parameter is not a kind of +Integer+.
-
# object = mock()
-
# object.expects(:method_1).with(kind_of(Integer))
-
# object.method_1('string')
-
# # error raised, because method_1 was not called with a kind of Integer
-
1
def kind_of(klass)
-
KindOf.new(klass)
-
end
-
-
# Parameter matcher which matches when actual parameter is a kind of specified class.
-
1
class KindOf < Base
-
-
# @private
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
parameter.kind_of?(@klass)
-
end
-
-
# @private
-
1
def mocha_inspect
-
"kind_of(#{@klass.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches if +matcher+ does *not* match.
-
#
-
# @param [Base] matcher matcher whose logic to invert.
-
# @return [Not] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual parameter does not include the value +1+.
-
# object = mock()
-
# object.expects(:method_1).with(Not(includes(1)))
-
# object.method_1([0, 2, 3])
-
# # no error raised
-
#
-
# @example Actual parameter does include the value +1+.
-
# object = mock()
-
# object.expects(:method_1).with(Not(includes(1)))
-
# object.method_1([0, 1, 2, 3])
-
# # error raised, because method_1 was not called with object not including 1
-
1
def Not(matcher)
-
Not.new(matcher)
-
end
-
-
# Parameter matcher which inverts the logic of the specified matcher using a logical NOT operation.
-
1
class Not < Base
-
-
# @private
-
1
def initialize(matcher)
-
@matcher = matcher
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
!@matcher.matches?([parameter])
-
end
-
-
# @private
-
1
def mocha_inspect
-
"Not(#{@matcher.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/equals'
-
-
1
module Mocha
-
-
1
module ObjectMethods
-
# @private
-
1
def to_matcher
-
576
Mocha::ParameterMatchers::Equals.new(self)
-
end
-
end
-
-
end
-
-
# @private
-
1
class Object
-
1
include Mocha::ObjectMethods
-
end
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches optional parameters if available.
-
#
-
# @param [*Array<Base>] matchers matchers for optional parameters.
-
# @return [Optionally] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Only the two required parameters are supplied and they both match their expected value.
-
# object = mock()
-
# object.expects(:method_1).with(1, 2, optionally(3, 4))
-
# object.method_1(1, 2)
-
# # no error raised
-
#
-
# @example Both required parameters and one of the optional parameters are supplied and they all match their expected value.
-
# object = mock()
-
# object.expects(:method_1).with(1, 2, optionally(3, 4))
-
# object.method_1(1, 2, 3)
-
# # no error raised
-
#
-
# @example Both required parameters and both of the optional parameters are supplied and they all match their expected value.
-
# object = mock()
-
# object.expects(:method_1).with(1, 2, optionally(3, 4))
-
# object.method_1(1, 2, 3, 4)
-
# # no error raised
-
#
-
# @example One of the actual optional parameters does not match the expected value.
-
# object = mock()
-
# object.expects(:method_1).with(1, 2, optionally(3, 4))
-
# object.method_1(1, 2, 3, 5)
-
# # error raised, because optional parameters did not match
-
1
def optionally(*matchers)
-
Optionally.new(*matchers)
-
end
-
-
# Parameter matcher which allows optional parameters to be specified.
-
1
class Optionally < Base
-
-
# @private
-
1
def initialize(*parameters)
-
@matchers = parameters.map { |parameter| parameter.to_matcher }
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
index = 0
-
while (available_parameters.length > 0) && (index < @matchers.length) do
-
matcher = @matchers[index]
-
return false unless matcher.matches?(available_parameters)
-
index += 1
-
end
-
return true
-
end
-
-
# @private
-
1
def mocha_inspect
-
"optionally(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
1
require 'uri'
-
-
1
module Mocha
-
1
module ParameterMatchers
-
-
# Matches a URI without regard to the ordering of parameters in the query string.
-
#
-
# @param [String] uri URI to match.
-
# @return [QueryStringMatches] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual URI has equivalent query string.
-
# object = mock()
-
# object.expects(:method_1).with(has_equivalent_query_string('http://example.com/foo?a=1&b=2))
-
# object.method_1('http://example.com/foo?b=2&a=1')
-
# # no error raised
-
#
-
# @example Actual URI does not have equivalent query string.
-
# object = mock()
-
# object.expects(:method_1).with(has_equivalent_query_string('http://example.com/foo?a=1&b=2))
-
# object.method_1('http://example.com/foo?a=1&b=3')
-
# # error raised, because the query parameters were different
-
1
def has_equivalent_query_string(uri)
-
QueryStringMatches.new(uri)
-
end
-
-
# Parameter matcher which matches URIs with equivalent query strings.
-
1
class QueryStringMatches < Base
-
-
# @private
-
1
def initialize(uri)
-
@uri = URI.parse(uri)
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
actual = explode(URI.parse(available_parameters.shift))
-
expected = explode(@uri)
-
actual == expected
-
end
-
-
# @private
-
1
def mocha_inspect
-
"has_equivalent_query_string(#{@uri.mocha_inspect})"
-
end
-
-
1
private
-
# @private
-
1
def explode(uri)
-
query_hash = (uri.query || '').split('&').inject({}){ |h, kv| h.merge(Hash[*kv.split('=')]) }
-
URI::Generic::COMPONENT.inject({}){ |h, k| h.merge(k => uri.__send__(k)) }.merge(:query => query_hash)
-
end
-
-
end
-
end
-
end
-
1
require 'mocha/parameter_matchers/base'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any object that matches +regexp+.
-
#
-
# @param [Regexp] regexp regular expression to match.
-
# @return [RegexpMatches] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual parameter is matched by specified regular expression.
-
# object = mock()
-
# object.expects(:method_1).with(regexp_matches(/e/))
-
# object.method_1('hello')
-
# # no error raised
-
#
-
# @example Actual parameter is not matched by specified regular expression.
-
# object = mock()
-
# object.expects(:method_1).with(regexp_matches(/a/))
-
# object.method_1('hello')
-
# # error raised, because method_1 was not called with a parameter that matched the
-
# # regular expression
-
1
def regexp_matches(regexp)
-
RegexpMatches.new(regexp)
-
end
-
-
# Parameter matcher which matches if specified regular expression matches actual paramter.
-
1
class RegexpMatches < Base
-
-
# @private
-
1
def initialize(regexp)
-
@regexp = regexp
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
return false unless parameter.respond_to?(:=~)
-
parameter =~ @regexp
-
end
-
-
# @private
-
1
def mocha_inspect
-
"regexp_matches(#{@regexp.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
1
require 'yaml'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any object that responds to +message+ with +result+. To put it another way, it tests the quack, not the duck.
-
#
-
# @param [Symbol] message method to invoke.
-
# @param [Object] result expected result of sending +message+.
-
# @return [RespondsWith] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual parameter responds with "FOO" when :upcase is invoked.
-
# object = mock()
-
# object.expects(:method_1).with(responds_with(:upcase, "FOO"))
-
# object.method_1("foo")
-
# # no error raised, because "foo".upcase == "FOO"
-
#
-
# @example Actual parameter does not respond with "FOO" when :upcase is invoked.
-
# object = mock()
-
# object.expects(:method_1).with(responds_with(:upcase, "BAR"))
-
# object.method_1("foo")
-
# # error raised, because "foo".upcase != "BAR"
-
1
def responds_with(message, result)
-
2
RespondsWith.new(message, result)
-
end
-
-
# Parameter matcher which matches if actual parameter returns expected result when specified method is invoked.
-
1
class RespondsWith < Base
-
-
# @private
-
1
def initialize(message, result)
-
2
@message, @result = message, result
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
parameter.__send__(@message) == @result
-
end
-
-
# @private
-
1
def mocha_inspect
-
"responds_with(#{@message.mocha_inspect}, #{@result.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/parameter_matchers/base'
-
1
require 'yaml'
-
-
1
module Mocha
-
-
1
module ParameterMatchers
-
-
# Matches any YAML that represents the specified +object+
-
#
-
# @param [Object] object object whose YAML to compare.
-
# @return [YamlEquivalent] parameter matcher.
-
#
-
# @see Expectation#with
-
#
-
# @example Actual parameter is YAML equivalent of specified +object+.
-
# object = mock()
-
# object.expects(:method_1).with(yaml_equivalent(1, 2, 3))
-
# object.method_1("--- \n- 1\n- 2\n- 3\n")
-
# # no error raised
-
#
-
# @example Actual parameter is not YAML equivalent of specified +object+.
-
# object = mock()
-
# object.expects(:method_1).with(yaml_equivalent(1, 2, 3))
-
# object.method_1("--- \n- 1\n- 2\n")
-
# # error raised, because method_1 was not called with YAML representing the specified Array
-
1
def yaml_equivalent(object)
-
YamlEquivalent.new(object)
-
end
-
-
# Parameter matcher which matches if actual parameter is YAML equivalent of specified object.
-
1
class YamlEquivalent < Base
-
-
# @private
-
1
def initialize(object)
-
@object = object
-
end
-
-
# @private
-
1
def matches?(available_parameters)
-
parameter = available_parameters.shift
-
@object == YAML.load(parameter)
-
end
-
-
# @private
-
1
def mocha_inspect
-
"yaml_equivalent(#{@object.mocha_inspect})"
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'mocha/inspect'
-
1
require 'mocha/parameter_matchers'
-
-
1
module Mocha
-
-
1
class ParametersMatcher
-
-
1
def initialize(expected_parameters = [ParameterMatchers::AnyParameters.new], &matching_block)
-
2966
@expected_parameters, @matching_block = expected_parameters, matching_block
-
end
-
-
1
def match?(actual_parameters = [])
-
810
if @matching_block
-
2
return @matching_block.call(*actual_parameters)
-
else
-
808
return parameters_match?(actual_parameters)
-
end
-
end
-
-
1
def parameters_match?(actual_parameters)
-
1728
matchers.all? { |matcher| matcher.matches?(actual_parameters) } && (actual_parameters.length == 0)
-
end
-
-
1
def mocha_inspect
-
signature = matchers.mocha_inspect
-
signature = signature.gsub(/^\[|\]$/, '')
-
signature = signature.gsub(/^\{|\}$/, '') if matchers.length == 1
-
"(#{signature})"
-
end
-
-
1
def matchers
-
1902
@expected_parameters.map { |parameter| parameter.to_matcher }
-
end
-
-
end
-
-
end
-
1
require 'mocha/single_return_value'
-
-
1
module Mocha
-
-
1
class ReturnValues
-
-
1
def self.build(*values)
-
4420
new(*values.map { |value| SingleReturnValue.new(value) })
-
end
-
-
1
attr_accessor :values
-
-
1
def initialize(*values)
-
6681
@values = values
-
end
-
-
1
def next
-
584
case @values.length
-
when 0 then nil
-
524
when 1 then @values.first.evaluate
-
2
else @values.shift.evaluate
-
end
-
end
-
-
1
def +(other)
-
2209
self.class.new(*(@values + other.values))
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
# Used to constrain the order in which expectations can occur.
-
#
-
# @see API#sequence
-
# @see Expectation#in_sequence
-
1
class Sequence
-
-
# @private
-
1
class InSequenceOrderingConstraint
-
-
1
def initialize(sequence, index)
-
@sequence, @index = sequence, index
-
end
-
-
1
def allows_invocation_now?
-
@sequence.satisfied_to_index?(@index)
-
end
-
-
1
def mocha_inspect
-
"in sequence #{@sequence.mocha_inspect}"
-
end
-
-
end
-
-
# @private
-
1
def initialize(name)
-
@name = name
-
@expectations = []
-
end
-
-
# @private
-
1
def constrain_as_next_in_sequence(expectation)
-
index = @expectations.length
-
@expectations << expectation
-
expectation.add_ordering_constraint(InSequenceOrderingConstraint.new(self, index))
-
end
-
-
# @private
-
1
def satisfied_to_index?(index)
-
@expectations[0...index].all? { |expectation| expectation.satisfied? }
-
end
-
-
# @private
-
1
def mocha_inspect
-
"#{@name.mocha_inspect}"
-
end
-
-
end
-
-
end
-
1
require 'mocha/is_a'
-
-
1
module Mocha
-
-
1
class SingleReturnValue
-
-
1
def initialize(value)
-
2211
@value = value
-
end
-
-
1
def evaluate
-
526
@value
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class SingleYield
-
-
1
attr_reader :parameters
-
-
1
def initialize(*parameters)
-
@parameters = parameters
-
end
-
-
1
def each
-
yield(@parameters)
-
end
-
-
end
-
-
end
-
-
1
module Mocha
-
-
# A state machine that is used to constrain the order of invocations.
-
# An invocation can be constrained to occur when a state {#is}, or {#is_not}, active.
-
1
class StateMachine
-
-
# Provides a mechanism to change the state of a {StateMachine} at some point in the future.
-
1
class State
-
-
# @private
-
1
def initialize(state_machine, state)
-
@state_machine, @state = state_machine, state
-
end
-
-
# @private
-
1
def activate
-
@state_machine.current_state = @state
-
end
-
-
# @private
-
1
def active?
-
@state_machine.current_state == @state
-
end
-
-
# @private
-
1
def mocha_inspect
-
"#{@state_machine.name} is #{@state.mocha_inspect}"
-
end
-
-
end
-
-
# Provides the ability to determine whether a {StateMachine} is in a specified state at some point in the future.
-
1
class StatePredicate
-
-
# @private
-
1
def initialize(state_machine, state)
-
@state_machine, @state = state_machine, state
-
end
-
-
# @private
-
1
def active?
-
@state_machine.current_state != @state
-
end
-
-
# @private
-
1
def mocha_inspect
-
"#{@state_machine.name} is not #{@state.mocha_inspect}"
-
end
-
-
end
-
-
# @private
-
1
attr_reader :name
-
-
# @private
-
1
attr_accessor :current_state
-
-
# @private
-
1
def initialize(name)
-
@name = name
-
@current_state = nil
-
end
-
-
# Put the {StateMachine} into the state specified by +initial_state_name+.
-
#
-
# @param [String] initial_state_name name of initial state
-
# @return [StateMachine] state machine, thereby allowing invocations of other {StateMachine} methods to be chained.
-
1
def starts_as(initial_state_name)
-
become(initial_state_name)
-
self
-
end
-
-
# Put the {StateMachine} into the +next_state_name+.
-
#
-
# @param [String] next_state_name name of new state
-
1
def become(next_state_name)
-
@current_state = next_state_name
-
end
-
-
# Provides a mechanism to change the {StateMachine} into the state specified by +state_name+ at some point in the future.
-
#
-
# Or provides a mechanism to determine whether the {StateMachine} is in the state specified by +state_name+ at some point in the future.
-
#
-
# @param [String] state_name name of new state
-
# @return [State] state which, when activated, will change the {StateMachine} into the state with the specified +state_name+.
-
1
def is(state_name)
-
State.new(self, state_name)
-
end
-
-
# Provides a mechanism to determine whether the {StateMachine} is not in the state specified by +state_name+ at some point in the future.
-
1
def is_not(state_name)
-
StatePredicate.new(self, state_name)
-
end
-
-
# @private
-
1
def mocha_inspect
-
if @current_state
-
"#{@name} is #{@current_state.mocha_inspect}"
-
else
-
"#{@name} has no current state"
-
end
-
end
-
-
end
-
-
end
-
1
require 'mocha/backtrace_filter'
-
-
1
module Mocha
-
-
# Exception raised when stubbing a particular method is not allowed.
-
#
-
# @see Configuration.prevent
-
1
class StubbingError < StandardError
-
-
# @private
-
1
def initialize(message = nil, backtrace = [])
-
super(message)
-
filter = BacktraceFilter.new
-
set_backtrace(filter.filtered(backtrace))
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
1
class Thrower
-
-
1
def initialize(tag, object = nil)
-
@tag, @object = tag, object
-
end
-
-
1
def evaluate
-
throw @tag, @object
-
end
-
-
end
-
-
end
-
1
module Mocha
-
-
# Exception raised when an unexpected method is invoked
-
1
class UnexpectedInvocation
-
-
# @private
-
1
def initialize(mock, symbol, *arguments)
-
@mock = mock
-
@method_matcher = MethodMatcher.new(symbol)
-
@parameters_matcher = ParametersMatcher.new(arguments)
-
end
-
-
# @private
-
1
def to_s
-
method_signature = "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}#{@parameters_matcher.mocha_inspect}"
-
"unexpected invocation: #{method_signature}\n"
-
end
-
-
end
-
-
end
-
1
require 'mocha/no_yields'
-
1
require 'mocha/single_yield'
-
1
require 'mocha/multiple_yields'
-
-
1
module Mocha
-
-
1
class YieldParameters
-
-
1
def initialize
-
2263
@parameter_groups = []
-
end
-
-
1
def next_invocation
-
case @parameter_groups.length
-
when 0 then NoYields.new
-
when 1 then @parameter_groups.first
-
else @parameter_groups.shift
-
end
-
end
-
-
1
def add(*parameters)
-
@parameter_groups << SingleYield.new(*parameters)
-
end
-
-
1
def multiple_add(*parameter_groups)
-
@parameter_groups << MultipleYields.new(*parameter_groups)
-
end
-
-
end
-
-
end
-
1
module Rack
-
-
1
class MockSession # :nodoc:
-
1
attr_writer :cookie_jar
-
1
attr_reader :default_host
-
-
1
def initialize(app, default_host = Rack::Test::DEFAULT_HOST)
-
510
@app = app
-
510
@after_request = []
-
510
@default_host = default_host
-
510
@last_request = nil
-
510
@last_response = nil
-
end
-
-
1
def after_request(&block)
-
@after_request << block
-
end
-
-
1
def clear_cookies
-
@cookie_jar = Rack::Test::CookieJar.new([], @default_host)
-
end
-
-
1
def set_cookie(cookie, uri = nil)
-
cookie_jar.merge(cookie, uri)
-
end
-
-
1
def request(uri, env)
-
787
env["HTTP_COOKIE"] ||= cookie_jar.for(uri)
-
787
@last_request = Rack::Request.new(env)
-
787
status, headers, body = @app.call(@last_request.env)
-
-
773
@last_response = MockResponse.new(status, headers, body, env["rack.errors"].flush)
-
773
body.close if body.respond_to?(:close)
-
-
773
cookie_jar.merge(last_response.headers["Set-Cookie"], uri)
-
-
773
@after_request.each { |hook| hook.call }
-
-
773
if @last_response.respond_to?(:finish)
-
773
@last_response.finish
-
else
-
@last_response
-
end
-
end
-
-
# Return the last request issued in the session. Raises an error if no
-
# requests have been sent yet.
-
1
def last_request
-
1524
raise Rack::Test::Error.new("No request yet. Request a page first.") unless @last_request
-
1524
@last_request
-
end
-
-
# Return the last response received in the session. Raises an error if
-
# no requests have been sent yet.
-
1
def last_response
-
3092
raise Rack::Test::Error.new("No response yet. Request a page first.") unless @last_response
-
3092
@last_response
-
end
-
-
1
def cookie_jar
-
1605
@cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
-
end
-
-
end
-
-
end
-
1
require "uri"
-
1
require "rack"
-
1
require "rack/mock_session"
-
1
require "rack/test/cookie_jar"
-
1
require "rack/test/mock_digest_request"
-
1
require "rack/test/utils"
-
1
require "rack/test/methods"
-
1
require "rack/test/uploaded_file"
-
-
1
module Rack
-
1
module Test
-
1
VERSION = "0.6.2"
-
-
1
DEFAULT_HOST = "example.org"
-
1
MULTIPART_BOUNDARY = "----------XnJLe9ZIbbGUYtzPQJ16u1"
-
-
# The common base class for exceptions raised by Rack::Test
-
1
class Error < StandardError; end
-
-
# This class represents a series of requests issued to a Rack app, sharing
-
# a single cookie jar
-
#
-
# Rack::Test::Session's methods are most often called through Rack::Test::Methods,
-
# which will automatically build a session when it's first used.
-
1
class Session
-
1
extend Forwardable
-
1
include Rack::Test::Utils
-
-
1
def_delegators :@rack_mock_session, :clear_cookies, :set_cookie, :last_response, :last_request
-
-
# Creates a Rack::Test::Session for a given Rack app or Rack::MockSession.
-
#
-
# Note: Generally, you won't need to initialize a Rack::Test::Session directly.
-
# Instead, you should include Rack::Test::Methods into your testing context.
-
# (See README.rdoc for an example)
-
1
def initialize(mock_session)
-
787
@headers = {}
-
-
787
if mock_session.is_a?(MockSession)
-
787
@rack_mock_session = mock_session
-
else
-
@rack_mock_session = MockSession.new(mock_session)
-
end
-
-
787
@default_host = @rack_mock_session.default_host
-
end
-
-
# Issue a GET request for the given URI with the given params and Rack
-
# environment. Stores the issues request object in #last_request and
-
# the app's response in #last_response. Yield #last_response to a block
-
# if given.
-
#
-
# Example:
-
# get "/"
-
1
def get(uri, params = {}, env = {}, &block)
-
11
env = env_for(uri, env.merge(:method => "GET", :params => params))
-
11
process_request(uri, env, &block)
-
end
-
-
# Issue a POST request for the given URI. See #get
-
#
-
# Example:
-
# post "/signup", "name" => "Bryan"
-
1
def post(uri, params = {}, env = {}, &block)
-
env = env_for(uri, env.merge(:method => "POST", :params => params))
-
process_request(uri, env, &block)
-
end
-
-
# Issue a PUT request for the given URI. See #get
-
#
-
# Example:
-
# put "/"
-
1
def put(uri, params = {}, env = {}, &block)
-
env = env_for(uri, env.merge(:method => "PUT", :params => params))
-
process_request(uri, env, &block)
-
end
-
-
# Issue a PATCH request for the given URI. See #get
-
#
-
# Example:
-
# patch "/"
-
1
def patch(uri, params = {}, env = {}, &block)
-
env = env_for(uri, env.merge(:method => "PATCH", :params => params))
-
process_request(uri, env, &block)
-
end
-
-
# Issue a DELETE request for the given URI. See #get
-
#
-
# Example:
-
# delete "/"
-
1
def delete(uri, params = {}, env = {}, &block)
-
env = env_for(uri, env.merge(:method => "DELETE", :params => params))
-
process_request(uri, env, &block)
-
end
-
-
# Issue an OPTIONS request for the given URI. See #get
-
#
-
# Example:
-
# options "/"
-
1
def options(uri, params = {}, env = {}, &block)
-
env = env_for(uri, env.merge(:method => "OPTIONS", :params => params))
-
process_request(uri, env, &block)
-
end
-
-
# Issue a HEAD request for the given URI. See #get
-
#
-
# Example:
-
# head "/"
-
1
def head(uri, params = {}, env = {}, &block)
-
env = env_for(uri, env.merge(:method => "HEAD", :params => params))
-
process_request(uri, env, &block)
-
end
-
-
# Issue a request to the Rack app for the given URI and optional Rack
-
# environment. Stores the issues request object in #last_request and
-
# the app's response in #last_response. Yield #last_response to a block
-
# if given.
-
#
-
# Example:
-
# request "/"
-
1
def request(uri, env = {}, &block)
-
776
env = env_for(uri, env)
-
776
process_request(uri, env, &block)
-
end
-
-
# Set a header to be included on all subsequent requests through the
-
# session. Use a value of nil to remove a previously configured header.
-
#
-
# In accordance with the Rack spec, headers will be included in the Rack
-
# environment hash in HTTP_USER_AGENT form.
-
#
-
# Example:
-
# header "User-Agent", "Firefox"
-
1
def header(name, value)
-
if value.nil?
-
@headers.delete(name)
-
else
-
@headers[name] = value
-
end
-
end
-
-
# Set the username and password for HTTP Basic authorization, to be
-
# included in subsequent requests in the HTTP_AUTHORIZATION header.
-
#
-
# Example:
-
# basic_authorize "bryan", "secret"
-
1
def basic_authorize(username, password)
-
encoded_login = ["#{username}:#{password}"].pack("m*")
-
header('Authorization', "Basic #{encoded_login}")
-
end
-
-
1
alias_method :authorize, :basic_authorize
-
-
# Set the username and password for HTTP Digest authorization, to be
-
# included in subsequent requests in the HTTP_AUTHORIZATION header.
-
#
-
# Example:
-
# digest_authorize "bryan", "secret"
-
1
def digest_authorize(username, password)
-
@digest_username = username
-
@digest_password = password
-
end
-
-
# Rack::Test will not follow any redirects automatically. This method
-
# will follow the redirect returned (including setting the Referer header
-
# on the new request) in the last response. If the last response was not
-
# a redirect, an error will be raised.
-
1
def follow_redirect!
-
unless last_response.redirect?
-
raise Error.new("Last response was not a redirect. Cannot follow_redirect!")
-
end
-
-
get(last_response["Location"], {}, { "HTTP_REFERER" => last_request.url })
-
end
-
-
1
private
-
-
1
def env_for(path, env)
-
787
uri = URI.parse(path)
-
787
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
-
787
uri.host ||= @default_host
-
-
787
env = default_env.merge(env)
-
-
787
env["HTTP_HOST"] ||= [uri.host, (uri.port if uri.port != uri.default_port)].compact.join(":")
-
-
787
env.update("HTTPS" => "on") if URI::HTTPS === uri
-
787
env["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" if env[:xhr]
-
-
# TODO: Remove this after Rack 1.1 has been released.
-
# Stringifying and upcasing methods has be commit upstream
-
787
env["REQUEST_METHOD"] ||= env[:method] ? env[:method].to_s.upcase : "GET"
-
-
787
if env["REQUEST_METHOD"] == "GET"
-
# merge :params with the query string
-
638
if params = env[:params]
-
96
params = parse_nested_query(params) if params.is_a?(String)
-
96
params.update(parse_nested_query(uri.query))
-
96
uri.query = build_nested_query(params)
-
end
-
elsif !env.has_key?(:input)
-
149
env["CONTENT_TYPE"] ||= "application/x-www-form-urlencoded"
-
-
149
if env[:params].is_a?(Hash)
-
3
if data = build_multipart(env[:params])
-
2
env[:input] = data
-
2
env["CONTENT_LENGTH"] ||= data.length.to_s
-
2
env["CONTENT_TYPE"] = "multipart/form-data; boundary=#{MULTIPART_BOUNDARY}"
-
else
-
1
env[:input] = params_to_string(env[:params])
-
end
-
else
-
146
env[:input] = env[:params]
-
end
-
end
-
-
787
env.delete(:params)
-
-
787
if env.has_key?(:cookie)
-
set_cookie(env.delete(:cookie), uri)
-
end
-
-
787
Rack::MockRequest.env_for(uri.to_s, env)
-
end
-
-
1
def process_request(uri, env)
-
787
uri = URI.parse(uri)
-
787
uri.host ||= @default_host
-
-
787
@rack_mock_session.request(uri, env)
-
-
773
if retry_with_digest_auth?(env)
-
auth_env = env.merge({
-
"HTTP_AUTHORIZATION" => digest_auth_header,
-
"rack-test.digest_auth_retry" => true
-
})
-
auth_env.delete('rack.request')
-
process_request(uri.path, auth_env)
-
else
-
773
yield last_response if block_given?
-
-
773
last_response
-
end
-
end
-
-
1
def digest_auth_header
-
challenge = last_response["WWW-Authenticate"].split(" ", 2).last
-
params = Rack::Auth::Digest::Params.parse(challenge)
-
-
params.merge!({
-
"username" => @digest_username,
-
"nc" => "00000001",
-
"cnonce" => "nonsensenonce",
-
"uri" => last_request.fullpath,
-
"method" => last_request.env["REQUEST_METHOD"],
-
})
-
-
params["response"] = MockDigestRequest.new(params).response(@digest_password)
-
-
"Digest #{params}"
-
end
-
-
1
def retry_with_digest_auth?(env)
-
last_response.status == 401 &&
-
773
digest_auth_configured? &&
-
!env["rack-test.digest_auth_retry"]
-
end
-
-
1
def digest_auth_configured?
-
@digest_username
-
end
-
-
1
def default_env
-
787
{ "rack.test" => true, "REMOTE_ADDR" => "127.0.0.1" }.merge(headers_for_env)
-
end
-
-
1
def headers_for_env
-
787
converted_headers = {}
-
-
787
@headers.each do |name, value|
-
env_key = name.upcase.gsub("-", "_")
-
env_key = "HTTP_" + env_key unless "CONTENT_TYPE" == env_key
-
converted_headers[env_key] = value
-
end
-
-
787
converted_headers
-
end
-
-
1
def params_to_string(params)
-
1
case params
-
1
when Hash then build_nested_query(params)
-
when nil then ""
-
else params
-
end
-
end
-
-
end
-
-
1
def self.encoding_aware_strings?
-
defined?(Encoding) && "".respond_to?(:encode)
-
end
-
-
end
-
end
-
#
-
# = base64.rb: methods for base64-encoding and -decoding strings
-
#
-
-
# The Base64 module provides for the encoding (#encode64, #strict_encode64,
-
# #urlsafe_encode64) and decoding (#decode64, #strict_decode64,
-
# #urlsafe_decode64) of binary data using a Base64 representation.
-
#
-
# == Example
-
#
-
# A simple encoding and decoding.
-
#
-
# require "base64"
-
#
-
# enc = Base64.encode64('Send reinforcements')
-
# # -> "U2VuZCByZWluZm9yY2VtZW50cw==\n"
-
# plain = Base64.decode64(enc)
-
# # -> "Send reinforcements"
-
#
-
# The purpose of using base64 to encode data is that it translates any
-
# binary data into purely printable characters.
-
-
1
module Base64
-
1
module_function
-
-
# Returns the Base64-encoded version of +bin+.
-
# This method complies with RFC 2045.
-
# Line feeds are added to every 60 encoded charactors.
-
#
-
# require 'base64'
-
# Base64.encode64("Now is the time for all good coders\nto learn Ruby")
-
#
-
# <i>Generates:</i>
-
#
-
# Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g
-
# UnVieQ==
-
1
def encode64(bin)
-
28
[bin].pack("m")
-
end
-
-
# Returns the Base64-decoded version of +str+.
-
# This method complies with RFC 2045.
-
# Characters outside the base alphabet are ignored.
-
#
-
# require 'base64'
-
# str = 'VGhpcyBpcyBsaW5lIG9uZQpUaGlzIG' +
-
# 'lzIGxpbmUgdHdvClRoaXMgaXMgbGlu' +
-
# 'ZSB0aHJlZQpBbmQgc28gb24uLi4K'
-
# puts Base64.decode64(str)
-
#
-
# <i>Generates:</i>
-
#
-
# This is line one
-
# This is line two
-
# This is line three
-
# And so on...
-
1
def decode64(str)
-
74
str.unpack("m").first
-
end
-
-
# Returns the Base64-encoded version of +bin+.
-
# This method complies with RFC 4648.
-
# No line feeds are added.
-
1
def strict_encode64(bin)
-
111
[bin].pack("m0")
-
end
-
-
# Returns the Base64-decoded version of +str+.
-
# This method complies with RFC 4648.
-
# ArgumentError is raised if +str+ is incorrectly padded or contains
-
# non-alphabet characters. Note that CR or LF are also rejected.
-
1
def strict_decode64(str)
-
str.unpack("m0").first
-
end
-
-
# Returns the Base64-encoded version of +bin+.
-
# This method complies with ``Base 64 Encoding with URL and Filename Safe
-
# Alphabet'' in RFC 4648.
-
# The alphabet uses '-' instead of '+' and '_' instead of '/'.
-
1
def urlsafe_encode64(bin)
-
strict_encode64(bin).tr("+/", "-_")
-
end
-
-
# Returns the Base64-decoded version of +str+.
-
# This method complies with ``Base 64 Encoding with URL and Filename Safe
-
# Alphabet'' in RFC 4648.
-
# The alphabet uses '-' instead of '+' and '_' instead of '/'.
-
1
def urlsafe_decode64(str)
-
strict_decode64(str.tr("-_", "+/"))
-
end
-
end
-
#--
-
# benchmark.rb - a performance benchmarking library
-
#
-
# $Id: benchmark.rb 32269 2011-06-28 06:09:46Z naruse $
-
#
-
# Created by Gotoken (gotoken@notwork.org).
-
#
-
# Documentation by Gotoken (original RD), Lyle Johnson (RDoc conversion), and
-
# Gavin Sinclair (editing).
-
#++
-
#
-
# == Overview
-
#
-
# The Benchmark module provides methods for benchmarking Ruby code, giving
-
# detailed reports on the time taken for each task.
-
#
-
-
# The Benchmark module provides methods to measure and report the time
-
# used to execute Ruby code.
-
#
-
# * Measure the time to construct the string given by the expression
-
# <tt>"a"*1_000_000</tt>:
-
#
-
# require 'benchmark'
-
#
-
# puts Benchmark.measure { "a"*1_000_000 }
-
#
-
# On my machine (FreeBSD 3.2 on P5, 100MHz) this generates:
-
#
-
# 1.166667 0.050000 1.216667 ( 0.571355)
-
#
-
# This report shows the user CPU time, system CPU time, the sum of
-
# the user and system CPU times, and the elapsed real time. The unit
-
# of time is seconds.
-
#
-
# * Do some experiments sequentially using the #bm method:
-
#
-
# require 'benchmark'
-
#
-
# n = 50000
-
# Benchmark.bm do |x|
-
# x.report { for i in 1..n; a = "1"; end }
-
# x.report { n.times do ; a = "1"; end }
-
# x.report { 1.upto(n) do ; a = "1"; end }
-
# end
-
#
-
# The result:
-
#
-
# user system total real
-
# 1.033333 0.016667 1.016667 ( 0.492106)
-
# 1.483333 0.000000 1.483333 ( 0.694605)
-
# 1.516667 0.000000 1.516667 ( 0.711077)
-
#
-
# * Continuing the previous example, put a label in each report:
-
#
-
# require 'benchmark'
-
#
-
# n = 50000
-
# Benchmark.bm(7) do |x|
-
# x.report("for:") { for i in 1..n; a = "1"; end }
-
# x.report("times:") { n.times do ; a = "1"; end }
-
# x.report("upto:") { 1.upto(n) do ; a = "1"; end }
-
# end
-
#
-
# The result:
-
#
-
# user system total real
-
# for: 1.050000 0.000000 1.050000 ( 0.503462)
-
# times: 1.533333 0.016667 1.550000 ( 0.735473)
-
# upto: 1.500000 0.016667 1.516667 ( 0.711239)
-
#
-
#
-
# * The times for some benchmarks depend on the order in which items
-
# are run. These differences are due to the cost of memory
-
# allocation and garbage collection. To avoid these discrepancies,
-
# the #bmbm method is provided. For example, to compare ways to
-
# sort an array of floats:
-
#
-
# require 'benchmark'
-
#
-
# array = (1..1000000).map { rand }
-
#
-
# Benchmark.bmbm do |x|
-
# x.report("sort!") { array.dup.sort! }
-
# x.report("sort") { array.dup.sort }
-
# end
-
#
-
# The result:
-
#
-
# Rehearsal -----------------------------------------
-
# sort! 11.928000 0.010000 11.938000 ( 12.756000)
-
# sort 13.048000 0.020000 13.068000 ( 13.857000)
-
# ------------------------------- total: 25.006000sec
-
#
-
# user system total real
-
# sort! 12.959000 0.010000 12.969000 ( 13.793000)
-
# sort 12.007000 0.000000 12.007000 ( 12.791000)
-
#
-
#
-
# * Report statistics of sequential experiments with unique labels,
-
# using the #benchmark method:
-
#
-
# require 'benchmark'
-
# include Benchmark # we need the CAPTION and FORMAT constants
-
#
-
# n = 50000
-
# Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
-
# tf = x.report("for:") { for i in 1..n; a = "1"; end }
-
# tt = x.report("times:") { n.times do ; a = "1"; end }
-
# tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
-
# [tf+tt+tu, (tf+tt+tu)/3]
-
# end
-
#
-
# The result:
-
#
-
# user system total real
-
# for: 1.016667 0.016667 1.033333 ( 0.485749)
-
# times: 1.450000 0.016667 1.466667 ( 0.681367)
-
# upto: 1.533333 0.000000 1.533333 ( 0.722166)
-
# >total: 4.000000 0.033333 4.033333 ( 1.889282)
-
# >avg: 1.333333 0.011111 1.344444 ( 0.629761)
-
-
1
module Benchmark
-
-
1
BENCHMARK_VERSION = "2002-04-25" #:nodoc"
-
-
# Invokes the block with a <tt>Benchmark::Report</tt> object, which
-
# may be used to collect and report on the results of individual
-
# benchmark tests. Reserves <i>label_width</i> leading spaces for
-
# labels on each line. Prints _caption_ at the top of the
-
# report, and uses _format_ to format each line.
-
# Returns an array of Benchmark::Tms objects.
-
#
-
# If the block returns an array of
-
# <tt>Benchmark::Tms</tt> objects, these will be used to format
-
# additional lines of output. If _label_ parameters are
-
# given, these are used to label these extra lines.
-
#
-
# _Note_: Other methods provide a simpler interface to this one, and are
-
# suitable for nearly all benchmarking requirements. See the examples in
-
# Benchmark, and the #bm and #bmbm methods.
-
#
-
# Example:
-
#
-
# require 'benchmark'
-
# include Benchmark # we need the CAPTION and FORMAT constants
-
#
-
# n = 50000
-
# Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
-
# tf = x.report("for:") { for i in 1..n; a = "1"; end }
-
# tt = x.report("times:") { n.times do ; a = "1"; end }
-
# tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
-
# [tf+tt+tu, (tf+tt+tu)/3]
-
# end
-
#
-
# <i>Generates:</i>
-
#
-
# user system total real
-
# for: 1.016667 0.016667 1.033333 ( 0.485749)
-
# times: 1.450000 0.016667 1.466667 ( 0.681367)
-
# upto: 1.533333 0.000000 1.533333 ( 0.722166)
-
# >total: 4.000000 0.033333 4.033333 ( 1.889282)
-
# >avg: 1.333333 0.011111 1.344444 ( 0.629761)
-
#
-
-
1
def benchmark(caption = "", label_width = nil, format = nil, *labels) # :yield: report
-
sync = STDOUT.sync
-
STDOUT.sync = true
-
label_width ||= 0
-
label_width += 1
-
format ||= FORMAT
-
print ' '*label_width + caption
-
report = Report.new(label_width, format)
-
results = yield(report)
-
Array === results and results.grep(Tms).each {|t|
-
print((labels.shift || t.label || "").ljust(label_width), t.format(format))
-
}
-
report.list
-
ensure
-
STDOUT.sync = sync unless sync.nil?
-
end
-
-
-
# A simple interface to the #benchmark method, #bm is generates sequential reports
-
# with labels. The parameters have the same meaning as for #benchmark.
-
#
-
# require 'benchmark'
-
#
-
# n = 50000
-
# Benchmark.bm(7) do |x|
-
# x.report("for:") { for i in 1..n; a = "1"; end }
-
# x.report("times:") { n.times do ; a = "1"; end }
-
# x.report("upto:") { 1.upto(n) do ; a = "1"; end }
-
# end
-
#
-
# <i>Generates:</i>
-
#
-
# user system total real
-
# for: 1.050000 0.000000 1.050000 ( 0.503462)
-
# times: 1.533333 0.016667 1.550000 ( 0.735473)
-
# upto: 1.500000 0.016667 1.516667 ( 0.711239)
-
#
-
-
1
def bm(label_width = 0, *labels, &blk) # :yield: report
-
benchmark(CAPTION, label_width, FORMAT, *labels, &blk)
-
end
-
-
-
# Sometimes benchmark results are skewed because code executed
-
# earlier encounters different garbage collection overheads than
-
# that run later. #bmbm attempts to minimize this effect by running
-
# the tests twice, the first time as a rehearsal in order to get the
-
# runtime environment stable, the second time for
-
# real. <tt>GC.start</tt> is executed before the start of each of
-
# the real timings; the cost of this is not included in the
-
# timings. In reality, though, there's only so much that #bmbm can
-
# do, and the results are not guaranteed to be isolated from garbage
-
# collection and other effects.
-
#
-
# Because #bmbm takes two passes through the tests, it can
-
# calculate the required label width.
-
#
-
# require 'benchmark'
-
#
-
# array = (1..1000000).map { rand }
-
#
-
# Benchmark.bmbm do |x|
-
# x.report("sort!") { array.dup.sort! }
-
# x.report("sort") { array.dup.sort }
-
# end
-
#
-
# <i>Generates:</i>
-
#
-
# Rehearsal -----------------------------------------
-
# sort! 11.928000 0.010000 11.938000 ( 12.756000)
-
# sort 13.048000 0.020000 13.068000 ( 13.857000)
-
# ------------------------------- total: 25.006000sec
-
#
-
# user system total real
-
# sort! 12.959000 0.010000 12.969000 ( 13.793000)
-
# sort 12.007000 0.000000 12.007000 ( 12.791000)
-
#
-
# #bmbm yields a Benchmark::Job object and returns an array of
-
# Benchmark::Tms objects.
-
#
-
1
def bmbm(width = 0, &blk) # :yield: job
-
job = Job.new(width)
-
yield(job)
-
width = job.width + 1
-
sync = STDOUT.sync
-
STDOUT.sync = true
-
-
# rehearsal
-
puts 'Rehearsal '.ljust(width+CAPTION.length,'-')
-
ets = job.list.inject(Tms.new) { |sum,(label,item)|
-
print label.ljust(width)
-
res = Benchmark.measure(&item)
-
print res.format
-
sum + res
-
}.format("total: %tsec")
-
print " #{ets}\n\n".rjust(width+CAPTION.length+2,'-')
-
-
# take
-
print ' '*width + CAPTION
-
job.list.map { |label,item|
-
GC.start
-
print label.ljust(width)
-
Benchmark.measure(label, &item).tap { |res| print res }
-
}
-
ensure
-
STDOUT.sync = sync unless sync.nil?
-
end
-
-
#
-
# Returns the time used to execute the given block as a
-
# Benchmark::Tms object.
-
#
-
1
def measure(label = "") # :yield:
-
t0, r0 = Process.times, Time.now
-
yield
-
t1, r1 = Process.times, Time.now
-
Benchmark::Tms.new(t1.utime - t0.utime,
-
t1.stime - t0.stime,
-
t1.cutime - t0.cutime,
-
t1.cstime - t0.cstime,
-
r1.to_f - r0.to_f,
-
label)
-
end
-
-
#
-
# Returns the elapsed real time used to execute the given block.
-
#
-
1
def realtime # :yield:
-
1057
r0 = Time.now
-
1057
yield
-
1015
Time.now - r0
-
end
-
-
1
module_function :benchmark, :measure, :realtime, :bm, :bmbm
-
-
#
-
# A Job is a sequence of labelled blocks to be processed by the
-
# Benchmark.bmbm method. It is of little direct interest to the user.
-
#
-
1
class Job # :nodoc:
-
#
-
# Returns an initialized Job instance.
-
# Usually, one doesn't call this method directly, as new
-
# Job objects are created by the #bmbm method.
-
# _width_ is a initial value for the label offset used in formatting;
-
# the #bmbm method passes its _width_ argument to this constructor.
-
#
-
1
def initialize(width)
-
@width = width
-
@list = []
-
end
-
-
#
-
# Registers the given label and block pair in the job list.
-
#
-
1
def item(label = "", &blk) # :yield:
-
raise ArgumentError, "no block" unless block_given?
-
label = label.to_s
-
w = label.length
-
@width = w if @width < w
-
@list << [label, blk]
-
self
-
end
-
-
1
alias report item
-
-
# An array of 2-element arrays, consisting of label and block pairs.
-
1
attr_reader :list
-
-
# Length of the widest label in the #list.
-
1
attr_reader :width
-
end
-
-
#
-
# This class is used by the Benchmark.benchmark and Benchmark.bm methods.
-
# It is of little direct interest to the user.
-
#
-
1
class Report # :nodoc:
-
#
-
# Returns an initialized Report instance.
-
# Usually, one doesn't call this method directly, as new
-
# Report objects are created by the #benchmark and #bm methods.
-
# _width_ and _format_ are the label offset and
-
# format string used by Tms#format.
-
#
-
1
def initialize(width = 0, format = nil)
-
@width, @format, @list = width, format, []
-
end
-
-
#
-
# Prints the _label_ and measured time for the block,
-
# formatted by _format_. See Tms#format for the
-
# formatting rules.
-
#
-
1
def item(label = "", *format, &blk) # :yield:
-
print label.to_s.ljust(@width)
-
@list << res = Benchmark.measure(label, &blk)
-
print res.format(@format, *format)
-
res
-
end
-
-
1
alias report item
-
-
# An array of Benchmark::Tms objects representing each item.
-
1
attr_reader :list
-
end
-
-
-
-
#
-
# A data object, representing the times associated with a benchmark
-
# measurement.
-
#
-
1
class Tms
-
-
# Default caption, see also Benchmark::CAPTION
-
1
CAPTION = " user system total real\n"
-
-
# Default format string, see also Benchmark::FORMAT
-
1
FORMAT = "%10.6u %10.6y %10.6t %10.6r\n"
-
-
# User CPU time
-
1
attr_reader :utime
-
-
# System CPU time
-
1
attr_reader :stime
-
-
# User CPU time of children
-
1
attr_reader :cutime
-
-
# System CPU time of children
-
1
attr_reader :cstime
-
-
# Elapsed real time
-
1
attr_reader :real
-
-
# Total time, that is _utime_ + _stime_ + _cutime_ + _cstime_
-
1
attr_reader :total
-
-
# Label
-
1
attr_reader :label
-
-
#
-
# Returns an initialized Tms object which has
-
# _utime_ as the user CPU time, _stime_ as the system CPU time,
-
# _cutime_ as the children's user CPU time, _cstime_ as the children's
-
# system CPU time, _real_ as the elapsed real time and _label_ as the label.
-
#
-
1
def initialize(utime = 0.0, stime = 0.0, cutime = 0.0, cstime = 0.0, real = 0.0, label = nil)
-
@utime, @stime, @cutime, @cstime, @real, @label = utime, stime, cutime, cstime, real, label.to_s
-
@total = @utime + @stime + @cutime + @cstime
-
end
-
-
#
-
# Returns a new Tms object whose times are the sum of the times for this
-
# Tms object, plus the time required to execute the code block (_blk_).
-
#
-
1
def add(&blk) # :yield:
-
self + Benchmark.measure(&blk)
-
end
-
-
#
-
# An in-place version of #add.
-
#
-
1
def add!(&blk)
-
t = Benchmark.measure(&blk)
-
@utime = utime + t.utime
-
@stime = stime + t.stime
-
@cutime = cutime + t.cutime
-
@cstime = cstime + t.cstime
-
@real = real + t.real
-
self
-
end
-
-
#
-
# Returns a new Tms object obtained by memberwise summation
-
# of the individual times for this Tms object with those of the other
-
# Tms object.
-
# This method and #/() are useful for taking statistics.
-
#
-
1
def +(other); memberwise(:+, other) end
-
-
#
-
# Returns a new Tms object obtained by memberwise subtraction
-
# of the individual times for the other Tms object from those of this
-
# Tms object.
-
#
-
1
def -(other); memberwise(:-, other) end
-
-
#
-
# Returns a new Tms object obtained by memberwise multiplication
-
# of the individual times for this Tms object by _x_.
-
#
-
1
def *(x); memberwise(:*, x) end
-
-
#
-
# Returns a new Tms object obtained by memberwise division
-
# of the individual times for this Tms object by _x_.
-
# This method and #+() are useful for taking statistics.
-
#
-
1
def /(x); memberwise(:/, x) end
-
-
#
-
# Returns the contents of this Tms object as
-
# a formatted string, according to a format string
-
# like that passed to Kernel.format. In addition, #format
-
# accepts the following extensions:
-
#
-
# <tt>%u</tt>:: Replaced by the user CPU time, as reported by Tms#utime.
-
# <tt>%y</tt>:: Replaced by the system CPU time, as reported by #stime (Mnemonic: y of "s*y*stem")
-
# <tt>%U</tt>:: Replaced by the children's user CPU time, as reported by Tms#cutime
-
# <tt>%Y</tt>:: Replaced by the children's system CPU time, as reported by Tms#cstime
-
# <tt>%t</tt>:: Replaced by the total CPU time, as reported by Tms#total
-
# <tt>%r</tt>:: Replaced by the elapsed real time, as reported by Tms#real
-
# <tt>%n</tt>:: Replaced by the label string, as reported by Tms#label (Mnemonic: n of "*n*ame")
-
#
-
# If _format_ is not given, FORMAT is used as default value, detailing the
-
# user, system and real elapsed time.
-
#
-
1
def format(format = nil, *args)
-
str = (format || FORMAT).dup
-
str.gsub!(/(%[-+\.\d]*)n/) { "#{$1}s" % label }
-
str.gsub!(/(%[-+\.\d]*)u/) { "#{$1}f" % utime }
-
str.gsub!(/(%[-+\.\d]*)y/) { "#{$1}f" % stime }
-
str.gsub!(/(%[-+\.\d]*)U/) { "#{$1}f" % cutime }
-
str.gsub!(/(%[-+\.\d]*)Y/) { "#{$1}f" % cstime }
-
str.gsub!(/(%[-+\.\d]*)t/) { "#{$1}f" % total }
-
str.gsub!(/(%[-+\.\d]*)r/) { "(#{$1}f)" % real }
-
format ? str % args : str
-
end
-
-
#
-
# Same as #format.
-
#
-
1
def to_s
-
format
-
end
-
-
#
-
# Returns a new 6-element array, consisting of the
-
# label, user CPU time, system CPU time, children's
-
# user CPU time, children's system CPU time and elapsed
-
# real time.
-
#
-
1
def to_a
-
[@label, @utime, @stime, @cutime, @cstime, @real]
-
end
-
-
1
protected
-
-
#
-
# Returns a new Tms object obtained by memberwise operation +op+
-
# of the individual times for this Tms object with those of the other
-
# Tms object.
-
#
-
# +op+ can be a mathematical operation such as <tt>+</tt>, <tt>-</tt>,
-
# <tt>*</tt>, <tt>/</tt>
-
#
-
1
def memberwise(op, x)
-
case x
-
when Benchmark::Tms
-
Benchmark::Tms.new(utime.__send__(op, x.utime),
-
stime.__send__(op, x.stime),
-
cutime.__send__(op, x.cutime),
-
cstime.__send__(op, x.cstime),
-
real.__send__(op, x.real)
-
)
-
else
-
Benchmark::Tms.new(utime.__send__(op, x),
-
stime.__send__(op, x),
-
cutime.__send__(op, x),
-
cstime.__send__(op, x),
-
real.__send__(op, x)
-
)
-
end
-
end
-
end
-
-
# The default caption string (heading above the output times).
-
1
CAPTION = Benchmark::Tms::CAPTION
-
-
# The default format string used to display times. See also Benchmark::Tms#format.
-
1
FORMAT = Benchmark::Tms::FORMAT
-
end
-
-
1
if __FILE__ == $0
-
include Benchmark
-
-
n = ARGV[0].to_i.nonzero? || 50000
-
puts %Q([#{n} times iterations of `a = "1"'])
-
benchmark(" " + CAPTION, 7, FORMAT) do |x|
-
x.report("for:") {for _ in 1..n; _ = "1"; end} # Benchmark.measure
-
x.report("times:") {n.times do ; _ = "1"; end}
-
x.report("upto:") {1.upto(n) do ; _ = "1"; end}
-
end
-
-
benchmark do
-
[
-
measure{for _ in 1..n; _ = "1"; end}, # Benchmark.measure
-
measure{n.times do ; _ = "1"; end},
-
measure{1.upto(n) do ; _ = "1"; end}
-
]
-
end
-
end
-
1
class Integer < Numeric
-
# call-seq:
-
# int.to_d -> bigdecimal
-
#
-
# Convert +int+ to a BigDecimal and return it.
-
#
-
# require 'bigdecimal'
-
# require 'bigdecimal/util'
-
#
-
# 42.to_d
-
# # => #<BigDecimal:1008ef070,'0.42E2',9(36)>
-
#
-
1
def to_d
-
BigDecimal(self)
-
end
-
end
-
-
1
class Float < Numeric
-
# call-seq:
-
# flt.to_d(precision=nil) -> bigdecimal
-
#
-
# Convert +flt+ to a BigDecimal and return it.
-
#
-
# require 'bigdecimal'
-
# require 'bigdecimal/util'
-
#
-
# 0.5.to_d
-
# # => #<BigDecimal:1dc69e0,'0.5E0',9(18)>
-
#
-
1
def to_d(precision=nil)
-
BigDecimal(self, precision || Float::DIG+1)
-
end
-
end
-
-
1
class String
-
# call-seq:
-
# string.to_d -> bigdecimal
-
#
-
# Convert +string+ to a BigDecimal and return it.
-
#
-
# require 'bigdecimal'
-
# require 'bigdecimal/util'
-
#
-
# "0.5".to_d
-
# # => #<BigDecimal:1dc69e0,'0.5E0',9(18)>
-
#
-
1
def to_d
-
BigDecimal(self)
-
end
-
end
-
-
1
class BigDecimal < Numeric
-
# call-seq:
-
# a.to_digits -> string
-
#
-
# Converts a BigDecimal to a String of the form "nnnnnn.mmm".
-
# This method is deprecated; use BigDecimal#to_s("F") instead.
-
#
-
# require 'bigdecimal'
-
# require 'bigdecimal/util'
-
#
-
# d = BigDecimal.new("3.14")
-
# d.to_digits
-
# # => "3.14"
-
1
def to_digits
-
if self.nan? || self.infinite? || self.zero?
-
self.to_s
-
else
-
i = self.to_i.to_s
-
_,f,_,z = self.frac.split
-
i + "." + ("0"*(-z)) + f
-
end
-
end
-
-
# call-seq:
-
# a.to_d -> bigdecimal
-
#
-
# Returns self.
-
1
def to_d
-
self
-
end
-
end
-
-
1
class Rational < Numeric
-
# call-seq:
-
# r.to_d(sig) -> bigdecimal
-
#
-
# Converts a Rational to a BigDecimal. Takes an optional parameter +sig+ to
-
# limit the amount of significant digits.
-
# If a negative precision is given, raise ArgumentError.
-
# The zero precision and implicit precision is deprecated.
-
#
-
# r = (22/7.0).to_r
-
# # => (7077085128725065/2251799813685248)
-
# r.to_d
-
# # => #<BigDecimal:1a52bd8,'0.3142857142 8571427937 0154144999 105E1',45(63)>
-
# r.to_d(3)
-
# # => #<BigDecimal:1a44d08,'0.314E1',18(36)>
-
1
def to_d(precision=0)
-
if precision < 0
-
raise ArgumentError, "negative precision"
-
elsif precision == 0
-
warn "zero and implicit precision is deprecated."
-
precision = BigDecimal.double_fig*2+1
-
end
-
num = self.numerator
-
BigDecimal(num).div(self.denominator, precision)
-
end
-
end
-
# = delegate -- Support for the Delegation Pattern
-
#
-
# Documentation by James Edward Gray II and Gavin Sinclair
-
-
##
-
# This library provides three different ways to delegate method calls to an
-
# object. The easiest to use is SimpleDelegator. Pass an object to the
-
# constructor and all methods supported by the object will be delegated. This
-
# object can be changed later.
-
#
-
# Going a step further, the top level DelegateClass method allows you to easily
-
# setup delegation through class inheritance. This is considerably more
-
# flexible and thus probably the most common use for this library.
-
#
-
# Finally, if you need full control over the delegation scheme, you can inherit
-
# from the abstract class Delegator and customize as needed. (If you find
-
# yourself needing this control, have a look at Forwardable which is also in
-
# the standard library. It may suit your needs better.)
-
#
-
# SimpleDelegator's implementation serves as a nice example if the use of
-
# Delegator:
-
#
-
# class SimpleDelegator < Delegator
-
# def initialize(obj)
-
# super # pass obj to Delegator constructor, required
-
# @delegate_sd_obj = obj # store obj for future use
-
# end
-
#
-
# def __getobj__
-
# @delegate_sd_obj # return object we are delegating to, required
-
# end
-
#
-
# def __setobj__(obj)
-
# @delegate_sd_obj = obj # change delegation object,
-
# # a feature we're providing
-
# end
-
# end
-
#
-
# == Notes
-
#
-
# Be advised, RDoc will not detect delegated methods.
-
#
-
1
class Delegator < BasicObject
-
1
kernel = ::Kernel.dup
-
1
kernel.class_eval do
-
1
[:to_s,:inspect,:=~,:!~,:===,:<=>,:eql?,:hash].each do |m|
-
8
undef_method m
-
end
-
end
-
1
include kernel
-
-
# :stopdoc:
-
1
def self.const_missing(n)
-
23370
::Object.const_get(n)
-
end
-
# :startdoc:
-
-
#
-
# Pass in the _obj_ to delegate method calls to. All methods supported by
-
# _obj_ will be delegated to.
-
#
-
1
def initialize(obj)
-
3371
__setobj__(obj)
-
end
-
-
#
-
# Handles the magic of delegation through \_\_getobj\_\_.
-
#
-
1
def method_missing(m, *args, &block)
-
298
target = self.__getobj__
-
298
begin
-
298
target.respond_to?(m) ? target.__send__(m, *args, &block) : super(m, *args, &block)
-
ensure
-
298
$@.delete_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@
-
end
-
end
-
-
#
-
# Checks for a method provided by this the delegate object by forwarding the
-
# call through \_\_getobj\_\_.
-
#
-
1
def respond_to_missing?(m, include_private)
-
2
r = self.__getobj__.respond_to?(m, include_private)
-
2
if r && include_private && !self.__getobj__.respond_to?(m, false)
-
warn "#{caller(3)[0]}: delegator does not forward private method \##{m}"
-
return false
-
end
-
2
r
-
end
-
-
#
-
# Returns the methods available to this delegate object as the union
-
# of this object's and \_\_getobj\_\_ methods.
-
#
-
1
def methods
-
__getobj__.methods | super
-
end
-
-
#
-
# Returns the methods available to this delegate object as the union
-
# of this object's and \_\_getobj\_\_ public methods.
-
#
-
1
def public_methods(all=true)
-
__getobj__.public_methods(all) | super
-
end
-
-
#
-
# Returns the methods available to this delegate object as the union
-
# of this object's and \_\_getobj\_\_ protected methods.
-
#
-
1
def protected_methods(all=true)
-
__getobj__.protected_methods(all) | super
-
end
-
-
# Note: no need to specialize private_methods, since they are not forwarded
-
-
#
-
# Returns true if two objects are considered of equal value.
-
#
-
1
def ==(obj)
-
2
return true if obj.equal?(self)
-
2
self.__getobj__ == obj
-
end
-
-
#
-
# Returns true if two objects are not considered of equal value.
-
#
-
1
def !=(obj)
-
return false if obj.equal?(self)
-
__getobj__ != obj
-
end
-
-
1
def !
-
!__getobj__
-
end
-
-
#
-
# This method must be overridden by subclasses and should return the object
-
# method calls are being delegated to.
-
#
-
1
def __getobj__
-
raise NotImplementedError, "need to define `__getobj__'"
-
end
-
-
#
-
# This method must be overridden by subclasses and change the object delegate
-
# to _obj_.
-
#
-
1
def __setobj__(obj)
-
raise NotImplementedError, "need to define `__setobj__'"
-
end
-
-
#
-
# Serialization support for the object returned by \_\_getobj\_\_.
-
#
-
1
def marshal_dump
-
ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var}
-
[
-
:__v2__,
-
ivars, ivars.map{|var| instance_variable_get(var)},
-
__getobj__
-
]
-
end
-
-
#
-
# Reinitializes delegation from a serialized object.
-
#
-
1
def marshal_load(data)
-
version, vars, values, obj = data
-
if version == :__v2__
-
vars.each_with_index{|var, i| instance_variable_set(var, values[i])}
-
__setobj__(obj)
-
else
-
__setobj__(data)
-
end
-
end
-
-
1
def initialize_clone(obj) # :nodoc:
-
self.__setobj__(obj.__getobj__.clone)
-
end
-
1
def initialize_dup(obj) # :nodoc:
-
self.__setobj__(obj.__getobj__.dup)
-
end
-
1
private :initialize_clone, :initialize_dup
-
-
##
-
# :method: trust
-
# Trust both the object returned by \_\_getobj\_\_ and self.
-
#
-
-
##
-
# :method: untrust
-
# Untrust both the object returned by \_\_getobj\_\_ and self.
-
#
-
-
##
-
# :method: taint
-
# Taint both the object returned by \_\_getobj\_\_ and self.
-
#
-
-
##
-
# :method: untaint
-
# Untaint both the object returned by \_\_getobj\_\_ and self.
-
#
-
-
##
-
# :method: freeze
-
# Freeze both the object returned by \_\_getobj\_\_ and self.
-
#
-
-
1
[:trust, :untrust, :taint, :untaint, :freeze].each do |method|
-
5
define_method method do
-
26
__getobj__.send(method)
-
26
super()
-
end
-
end
-
-
1
@delegator_api = self.public_instance_methods
-
1
def self.public_api # :nodoc:
-
2
@delegator_api
-
end
-
end
-
-
##
-
# A concrete implementation of Delegator, this class provides the means to
-
# delegate all supported method calls to the object passed into the constructor
-
# and even to change the object being delegated to at a later time with
-
# #__setobj__.
-
#
-
# Here's a simple example that takes advantage of the fact that
-
# SimpleDelegator's delegation object can be changed at any time.
-
#
-
# class Stats
-
# def initialize
-
# @source = SimpleDelegator.new([])
-
# end
-
#
-
# def stats(records)
-
# @source.__setobj__(records)
-
#
-
# "Elements: #{@source.size}\n" +
-
# " Non-Nil: #{@source.compact.size}\n" +
-
# " Unique: #{@source.uniq.size}\n"
-
# end
-
# end
-
#
-
# s = Stats.new
-
# puts s.stats(%w{James Edward Gray II})
-
# puts
-
# puts s.stats([1, 2, 3, nil, 4, 5, 1, 2])
-
#
-
# Prints:
-
#
-
# Elements: 4
-
# Non-Nil: 4
-
# Unique: 4
-
#
-
# Elements: 8
-
# Non-Nil: 7
-
# Unique: 6
-
#
-
1
class SimpleDelegator<Delegator
-
# Returns the current object method calls are being delegated to.
-
1
def __getobj__
-
290
@delegate_sd_obj
-
end
-
-
#
-
# Changes the delegate object to _obj_.
-
#
-
# It's important to note that this does *not* cause SimpleDelegator's methods
-
# to change. Because of this, you probably only want to change delegation
-
# to objects of the same type as the original delegate.
-
#
-
# Here's an example of changing the delegation object.
-
#
-
# names = SimpleDelegator.new(%w{James Edward Gray II})
-
# puts names[1] # => Edward
-
# names.__setobj__(%w{Gavin Sinclair})
-
# puts names[1] # => Sinclair
-
#
-
1
def __setobj__(obj)
-
25
raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
-
25
@delegate_sd_obj = obj
-
end
-
end
-
-
# :stopdoc:
-
1
def Delegator.delegating_block(mid)
-
282
lambda do |*args, &block|
-
6766
target = self.__getobj__
-
6766
begin
-
6766
target.__send__(mid, *args, &block)
-
ensure
-
6766
$@.delete_if {|t| /\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:/o =~ t} if $@
-
end
-
end
-
end
-
# :startdoc:
-
-
#
-
# The primary interface to this library. Use to setup delegation when defining
-
# your class.
-
#
-
# class MyClass < DelegateClass(ClassToDelegateTo) # Step 1
-
# def initialize
-
# super(obj_of_ClassToDelegateTo) # Step 2
-
# end
-
# end
-
#
-
# Here's a sample of use from Tempfile which is really a File object with a
-
# few special rules about storage location and when the File should be
-
# deleted. That makes for an almost textbook perfect example of how to use
-
# delegation.
-
#
-
# class Tempfile < DelegateClass(File)
-
# # constant and class member data initialization...
-
#
-
# def initialize(basename, tmpdir=Dir::tmpdir)
-
# # build up file path/name in var tmpname...
-
#
-
# @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
-
#
-
# # ...
-
#
-
# super(@tmpfile)
-
#
-
# # below this point, all methods of File are supported...
-
# end
-
#
-
# # ...
-
# end
-
#
-
1
def DelegateClass(superclass)
-
2
klass = Class.new(Delegator)
-
2
methods = superclass.instance_methods
-
2
methods -= ::Delegator.public_api
-
2
methods -= [:to_s,:inspect,:=~,:!~,:===]
-
2
klass.module_eval do
-
2
def __getobj__ # :nodoc:
-
6809
@delegate_dc_obj
-
end
-
2
def __setobj__(obj) # :nodoc:
-
3346
raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
-
3346
@delegate_dc_obj = obj
-
end
-
2
methods.each do |method|
-
282
define_method(method, Delegator.delegating_block(method))
-
end
-
end
-
2
klass.define_singleton_method :public_instance_methods do |all=true|
-
super(all) - superclass.protected_instance_methods
-
end
-
2
klass.define_singleton_method :protected_instance_methods do |all=true|
-
super(all) | superclass.protected_instance_methods
-
end
-
2
return klass
-
end
-
-
# :enddoc:
-
-
1
if __FILE__ == $0
-
class ExtArray<DelegateClass(Array)
-
def initialize()
-
super([])
-
end
-
end
-
-
ary = ExtArray.new
-
p ary.class
-
ary.push 25
-
p ary
-
ary.push 42
-
ary.each {|x| p x}
-
-
foo = Object.new
-
def foo.test
-
25
-
end
-
def foo.iter
-
yield self
-
end
-
def foo.error
-
raise 'this is OK'
-
end
-
foo2 = SimpleDelegator.new(foo)
-
p foo2
-
foo2.instance_eval{print "foo\n"}
-
p foo.test == foo2.test # => true
-
p foo2.iter{[55,true]} # => true
-
foo2.error # raise error!
-
end
-
# logger.rb - simple logging utility
-
# Copyright (C) 2000-2003, 2005, 2008, 2011 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
-
#
-
# Documentation:: NAKAMURA, Hiroshi and Gavin Sinclair
-
# License::
-
# You can redistribute it and/or modify it under the same terms of Ruby's
-
# license; either the dual license version in 2003, or any later version.
-
# Revision:: $Id: logger.rb 31641 2011-05-19 00:07:25Z nobu $
-
#
-
# A simple system for logging messages. See Logger for more documentation.
-
-
1
require 'monitor'
-
-
# == Description
-
#
-
# The Logger class provides a simple but sophisticated logging utility that
-
# you can use to output messages.
-
#
-
# The messages have associated levels, such as +INFO+ or +ERROR+ that indicate
-
# their importance. You can then give the Logger a level, and only messages
-
# at that level of higher will be printed.
-
#
-
# The levels are:
-
#
-
# +FATAL+:: an unhandleable error that results in a program crash
-
# +ERROR+:: a handleable error condition
-
# +WARN+:: a warning
-
# +INFO+:: generic (useful) information about system operation
-
# +DEBUG+:: low-level information for developers
-
#
-
# For instance, in a production system, you may have your Logger set to
-
# +INFO+ or even +WARN+
-
# When you are developing the system, however, you probably
-
# want to know about the program's internal state, and would set the Logger to
-
# +DEBUG+.
-
#
-
# *Note*: Logger does not escape or sanitize any messages passed to it.
-
# Developers should be aware of when potentially malicious data (user-input)
-
# is passed to Logger, and manually escape the untrusted data:
-
#
-
# logger.info("User-input: #{input.dump}")
-
# logger.info("User-input: %p" % input)
-
#
-
# You can use #formatter= for escaping all data.
-
#
-
# original_formatter = Logger::Formatter.new
-
# logger.formatter = proc { |severity, datetime, progname, msg|
-
# original_formatter.call(severity, datetime, progname, msg.dump)
-
# }
-
# logger.info(input)
-
#
-
# === Example
-
#
-
# This creates a logger to the standard output stream, with a level of +WARN+
-
#
-
# log = Logger.new(STDOUT)
-
# log.level = Logger::WARN
-
#
-
# log.debug("Created logger")
-
# log.info("Program started")
-
# log.warn("Nothing to do!")
-
#
-
# begin
-
# File.each_line(path) do |line|
-
# unless line =~ /^(\w+) = (.*)$/
-
# log.error("Line in wrong format: #{line}")
-
# end
-
# end
-
# rescue => err
-
# log.fatal("Caught exception; exiting")
-
# log.fatal(err)
-
# end
-
#
-
# Because the Logger's level is set to +WARN+, only the warning, error, and
-
# fatal messages are recorded. The debug and info messages are silently
-
# discarded.
-
#
-
# === Features
-
#
-
# There are several interesting features that Logger provides, like
-
# auto-rolling of log files, setting the format of log messages, and
-
# specifying a program name in conjunction with the message. The next section
-
# shows you how to achieve these things.
-
#
-
#
-
# == HOWTOs
-
#
-
# === How to create a logger
-
#
-
# The options below give you various choices, in more or less increasing
-
# complexity.
-
#
-
# 1. Create a logger which logs messages to STDERR/STDOUT.
-
#
-
# logger = Logger.new(STDERR)
-
# logger = Logger.new(STDOUT)
-
#
-
# 2. Create a logger for the file which has the specified name.
-
#
-
# logger = Logger.new('logfile.log')
-
#
-
# 3. Create a logger for the specified file.
-
#
-
# file = File.open('foo.log', File::WRONLY | File::APPEND)
-
# # To create new (and to remove old) logfile, add File::CREAT like;
-
# # file = open('foo.log', File::WRONLY | File::APPEND | File::CREAT)
-
# logger = Logger.new(file)
-
#
-
# 4. Create a logger which ages logfile once it reaches a certain size. Leave
-
# 10 "old log files" and each file is about 1,024,000 bytes.
-
#
-
# logger = Logger.new('foo.log', 10, 1024000)
-
#
-
# 5. Create a logger which ages logfile daily/weekly/monthly.
-
#
-
# logger = Logger.new('foo.log', 'daily')
-
# logger = Logger.new('foo.log', 'weekly')
-
# logger = Logger.new('foo.log', 'monthly')
-
#
-
# === How to log a message
-
#
-
# Notice the different methods (+fatal+, +error+, +info+) being used to log
-
# messages of various levels? Other methods in this family are +warn+ and
-
# +debug+. +add+ is used below to log a message of an arbitrary (perhaps
-
# dynamic) level.
-
#
-
# 1. Message in block.
-
#
-
# logger.fatal { "Argument 'foo' not given." }
-
#
-
# 2. Message as a string.
-
#
-
# logger.error "Argument #{ @foo } mismatch."
-
#
-
# 3. With progname.
-
#
-
# logger.info('initialize') { "Initializing..." }
-
#
-
# 4. With severity.
-
#
-
# logger.add(Logger::FATAL) { 'Fatal error!' }
-
#
-
# The block form allows you to create potentially complex log messages,
-
# but to delay their evaluation until and unless the message is
-
# logged. For example, if we have the following:
-
#
-
# logger.debug { "This is a " + potentially + " expensive operation" }
-
#
-
# If the logger's level is +INFO+ or higher, no debug messages will be logged,
-
# and the entire block will not even be evaluated. Compare to this:
-
#
-
# logger.debug("This is a " + potentially + " expensive operation")
-
#
-
# Here, the string concatenation is done every time, even if the log
-
# level is not set to show the debug message.
-
#
-
# === How to close a logger
-
#
-
# logger.close
-
#
-
# === Setting severity threshold
-
#
-
# 1. Original interface.
-
#
-
# logger.sev_threshold = Logger::WARN
-
#
-
# 2. Log4r (somewhat) compatible interface.
-
#
-
# logger.level = Logger::INFO
-
#
-
# DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
-
#
-
#
-
# == Format
-
#
-
# Log messages are rendered in the output stream in a certain format by
-
# default. The default format and a sample are shown below:
-
#
-
# Log format:
-
# SeverityID, [Date Time mSec #pid] SeverityLabel -- ProgName: message
-
#
-
# Log sample:
-
# I, [Wed Mar 03 02:34:24 JST 1999 895701 #19074] INFO -- Main: info.
-
#
-
# You may change the date and time format via #datetime_format=
-
#
-
# logger.datetime_format = "%Y-%m-%d %H:%M:%S"
-
# # e.g. "2004-01-03 00:54:26"
-
#
-
# Or, you may change the overall format with #formatter= method.
-
#
-
# logger.formatter = proc do |severity, datetime, progname, msg|
-
# "#{datetime}: #{msg}\n"
-
# end
-
# # e.g. "Thu Sep 22 08:51:08 GMT+9:00 2005: hello world"
-
#
-
1
class Logger
-
1
VERSION = "1.2.7"
-
1
_, name, rev = %w$Id: logger.rb 31641 2011-05-19 00:07:25Z nobu $
-
1
if name
-
1
name = name.chomp(",v")
-
else
-
name = File.basename(__FILE__)
-
end
-
1
rev ||= "v#{VERSION}"
-
1
ProgName = "#{name}/#{rev}"
-
-
1
class Error < RuntimeError # :nodoc:
-
end
-
# not used after 1.2.7. just for compat.
-
1
class ShiftingError < Error # :nodoc:
-
end
-
-
# Logging severity.
-
1
module Severity
-
# Low-level information, mostly for developers
-
1
DEBUG = 0
-
# generic, useful information about system operation
-
1
INFO = 1
-
# a warning
-
1
WARN = 2
-
# a handleable error condition
-
1
ERROR = 3
-
# an unhandleable error that results in a program crash
-
1
FATAL = 4
-
# an unknown message that should always be logged
-
1
UNKNOWN = 5
-
end
-
1
include Severity
-
-
# Logging severity threshold (e.g. <tt>Logger::INFO</tt>).
-
1
attr_accessor :level
-
-
# program name to include in log messages.
-
1
attr_accessor :progname
-
-
# Set date-time format.
-
#
-
# +datetime_format+:: A string suitable for passing to +strftime+.
-
1
def datetime_format=(datetime_format)
-
@default_formatter.datetime_format = datetime_format
-
end
-
-
# Returns the date format being used. See #datetime_format=
-
1
def datetime_format
-
@default_formatter.datetime_format
-
end
-
-
# Logging formatter, as a +Proc+ that will take four arguments and
-
# return the formatted message. The arguments are:
-
#
-
# +severity+:: The Severity of the log message
-
# +time+:: A Time instance representing when the message was logged
-
# +progname+:: The #progname configured, or passed to the logger method
-
# +msg+:: The _Object_ the user passed to the log message; not necessarily a
-
# String.
-
#
-
# The block should return an Object that can be written to the logging
-
# device via +write+. The default formatter is used when no formatter is
-
# set.
-
1
attr_accessor :formatter
-
-
1
alias sev_threshold level
-
1
alias sev_threshold= level=
-
-
# Returns +true+ iff the current severity level allows for the printing of
-
# +DEBUG+ messages.
-
1
def debug?; @level <= DEBUG; end
-
-
# Returns +true+ iff the current severity level allows for the printing of
-
# +INFO+ messages.
-
47
def info?; @level <= INFO; end
-
-
# Returns +true+ iff the current severity level allows for the printing of
-
# +WARN+ messages.
-
1
def warn?; @level <= WARN; end
-
-
# Returns +true+ iff the current severity level allows for the printing of
-
# +ERROR+ messages.
-
1
def error?; @level <= ERROR; end
-
-
# Returns +true+ iff the current severity level allows for the printing of
-
# +FATAL+ messages.
-
1
def fatal?; @level <= FATAL; end
-
-
#
-
# === Synopsis
-
#
-
# Logger.new(name, shift_age = 7, shift_size = 1048576)
-
# Logger.new(name, shift_age = 'weekly')
-
#
-
# === Args
-
#
-
# +logdev+::
-
# The log device. This is a filename (String) or IO object (typically
-
# +STDOUT+, +STDERR+, or an open file).
-
# +shift_age+::
-
# Number of old log files to keep, *or* frequency of rotation (+daily+,
-
# +weekly+ or +monthly+).
-
# +shift_size+::
-
# Maximum logfile size (only applies when +shift_age+ is a number).
-
#
-
# === Description
-
#
-
# Create an instance.
-
#
-
1
def initialize(logdev, shift_age = 0, shift_size = 1048576)
-
289
@progname = nil
-
289
@level = DEBUG
-
289
@default_formatter = Formatter.new
-
289
@formatter = nil
-
289
@logdev = nil
-
289
if logdev
-
12
@logdev = LogDevice.new(logdev, :shift_age => shift_age,
-
:shift_size => shift_size)
-
end
-
end
-
-
#
-
# === Synopsis
-
#
-
# Logger#add(severity, message = nil, progname = nil) { ... }
-
#
-
# === Args
-
#
-
# +severity+::
-
# Severity. Constants are defined in Logger namespace: +DEBUG+, +INFO+,
-
# +WARN+, +ERROR+, +FATAL+, or +UNKNOWN+.
-
# +message+::
-
# The log message. A String or Exception.
-
# +progname+::
-
# Program name string. Can be omitted. Treated as a message if no
-
# +message+ and +block+ are given.
-
# +block+::
-
# Can be omitted. Called to get a message string if +message+ is nil.
-
#
-
# === Return
-
#
-
# +true+ if successful, +false+ otherwise.
-
#
-
# When the given severity is not high enough (for this particular logger), log
-
# no message, and return +true+.
-
#
-
# === Description
-
#
-
# Log a message if the given severity is high enough. This is the generic
-
# logging method. Users will be more inclined to use #debug, #info, #warn,
-
# #error, and #fatal.
-
#
-
# <b>Message format</b>: +message+ can be any object, but it has to be
-
# converted to a String in order to log it. Generally, +inspect+ is used
-
# if the given object is not a String.
-
# A special case is an +Exception+ object, which will be printed in detail,
-
# including message, class, and backtrace. See #msg2str for the
-
# implementation if required.
-
#
-
# === Bugs
-
#
-
# * Logfile is not locked.
-
# * Append open does not need to lock file.
-
# * If the OS which supports multi I/O, records possibly be mixed.
-
#
-
1
def add(severity, message = nil, progname = nil, &block)
-
259
severity ||= UNKNOWN
-
259
if @logdev.nil? or severity < @level
-
238
return true
-
end
-
21
progname ||= @progname
-
21
if message.nil?
-
21
if block_given?
-
message = yield
-
else
-
21
message = progname
-
21
progname = @progname
-
end
-
end
-
21
@logdev.write(
-
format_message(format_severity(severity), Time.now, progname, message))
-
21
true
-
end
-
1
alias log add
-
-
#
-
# Dump given message to the log device without any formatting. If no log
-
# device exists, return +nil+.
-
#
-
1
def <<(msg)
-
unless @logdev.nil?
-
@logdev.write(msg)
-
end
-
end
-
-
#
-
# Log a +DEBUG+ message.
-
#
-
# See #info for more information.
-
#
-
1
def debug(progname = nil, &block)
-
15
add(DEBUG, nil, progname, &block)
-
end
-
-
#
-
# :call-seq:
-
# info(message)
-
# info(progname,&block)
-
#
-
# Log an +INFO+ message.
-
#
-
# +message+:: the message to log; does not need to be a String
-
# +progname+:: in the block form, this is the #progname to use in the
-
# the log message. The default can be set with #progname=
-
# <tt>&block</tt>:: evaluates to the message to log. This is not evaluated
-
# unless the logger's level is sufficient
-
# to log the message. This allows you to create
-
# potentially expensive logging messages that are
-
# only called when the logger is configured to show them.
-
#
-
# === Examples
-
#
-
# logger.info("MainApp") { "Received connection from #{ip}" }
-
# # ...
-
# logger.info "Waiting for input from user"
-
# # ...
-
# logger.info { "User typed #{input}" }
-
#
-
# You'll probably stick to the second form above, unless you want to provide a
-
# program name (which you can do with #progname= as well).
-
#
-
# === Return
-
#
-
# See #add.
-
#
-
1
def info(progname = nil, &block)
-
175
add(INFO, nil, progname, &block)
-
end
-
-
#
-
# Log a +WARN+ message.
-
#
-
# See #info for more information.
-
#
-
1
def warn(progname = nil, &block)
-
2
add(WARN, nil, progname, &block)
-
end
-
-
#
-
# Log an +ERROR+ message.
-
#
-
# See #info for more information.
-
#
-
1
def error(progname = nil, &block)
-
61
add(ERROR, nil, progname, &block)
-
end
-
-
#
-
# Log a +FATAL+ message.
-
#
-
# See #info for more information.
-
#
-
1
def fatal(progname = nil, &block)
-
6
add(FATAL, nil, progname, &block)
-
end
-
-
#
-
# Log an +UNKNOWN+ message. This will be printed no matter what the logger's
-
# level.
-
#
-
# See #info for more information.
-
#
-
1
def unknown(progname = nil, &block)
-
add(UNKNOWN, nil, progname, &block)
-
end
-
-
#
-
# Close the logging device.
-
#
-
1
def close
-
@logdev.close if @logdev
-
end
-
-
1
private
-
-
# Severity label for logging. (max 5 char)
-
1
SEV_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY)
-
-
1
def format_severity(severity)
-
21
SEV_LABEL[severity] || 'ANY'
-
end
-
-
1
def format_message(severity, datetime, progname, msg)
-
21
(@formatter || @default_formatter).call(severity, datetime, progname, msg)
-
end
-
-
-
# Default formatter for log messages
-
1
class Formatter
-
1
Format = "%s, [%s#%d] %5s -- %s: %s\n"
-
-
1
attr_accessor :datetime_format
-
-
1
def initialize
-
573
@datetime_format = nil
-
end
-
-
1
def call(severity, time, progname, msg)
-
Format % [severity[0..0], format_datetime(time), $$, severity, progname,
-
11
msg2str(msg)]
-
end
-
-
1
private
-
-
1
def format_datetime(time)
-
11
if @datetime_format.nil?
-
11
time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
-
else
-
time.strftime(@datetime_format)
-
end
-
end
-
-
1
def msg2str(msg)
-
11
case msg
-
when ::String
-
11
msg
-
when ::Exception
-
"#{ msg.message } (#{ msg.class })\n" <<
-
(msg.backtrace || []).join("\n")
-
else
-
msg.inspect
-
end
-
end
-
end
-
-
-
# Device used for logging messages.
-
1
class LogDevice
-
1
attr_reader :dev
-
1
attr_reader :filename
-
-
1
class LogDeviceMutex
-
1
include MonitorMixin
-
end
-
-
1
def initialize(log = nil, opt = {})
-
12
@dev = @filename = @shift_age = @shift_size = nil
-
12
@mutex = LogDeviceMutex.new
-
12
if log.respond_to?(:write) and log.respond_to?(:close)
-
12
@dev = log
-
else
-
@dev = open_logfile(log)
-
@dev.sync = true
-
@filename = log
-
@shift_age = opt[:shift_age] || 7
-
@shift_size = opt[:shift_size] || 1048576
-
end
-
end
-
-
1
def write(message)
-
21
begin
-
21
@mutex.synchronize do
-
21
if @shift_age and @dev.respond_to?(:stat)
-
begin
-
check_shift_log
-
rescue
-
warn("log shifting failed. #{$!}")
-
end
-
end
-
21
begin
-
21
@dev.write(message)
-
rescue
-
warn("log writing failed. #{$!}")
-
end
-
end
-
rescue Exception => ignored
-
warn("log writing failed. #{ignored}")
-
end
-
end
-
-
1
def close
-
begin
-
@mutex.synchronize do
-
@dev.close rescue nil
-
end
-
rescue Exception
-
@dev.close rescue nil
-
end
-
end
-
-
1
private
-
-
1
def open_logfile(filename)
-
if (FileTest.exist?(filename))
-
open(filename, (File::WRONLY | File::APPEND))
-
else
-
create_logfile(filename)
-
end
-
end
-
-
1
def create_logfile(filename)
-
logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT))
-
logdev.sync = true
-
add_log_header(logdev)
-
logdev
-
end
-
-
1
def add_log_header(file)
-
file.write(
-
"# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
-
)
-
end
-
-
1
SiD = 24 * 60 * 60
-
-
1
def check_shift_log
-
if @shift_age.is_a?(Integer)
-
# Note: always returns false if '0'.
-
if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
-
shift_log_age
-
end
-
else
-
now = Time.now
-
period_end = previous_period_end(now)
-
if @dev.stat.mtime <= period_end
-
shift_log_period(period_end)
-
end
-
end
-
end
-
-
1
def shift_log_age
-
(@shift_age-3).downto(0) do |i|
-
if FileTest.exist?("#{@filename}.#{i}")
-
File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}")
-
end
-
end
-
@dev.close rescue nil
-
File.rename("#{@filename}", "#{@filename}.0")
-
@dev = create_logfile(@filename)
-
return true
-
end
-
-
1
def shift_log_period(period_end)
-
postfix = period_end.strftime("%Y%m%d") # YYYYMMDD
-
age_file = "#{@filename}.#{postfix}"
-
if FileTest.exist?(age_file)
-
# try to avoid filename crash caused by Timestamp change.
-
idx = 0
-
# .99 can be overridden; avoid too much file search with 'loop do'
-
while idx < 100
-
idx += 1
-
age_file = "#{@filename}.#{postfix}.#{idx}"
-
break unless FileTest.exist?(age_file)
-
end
-
end
-
@dev.close rescue nil
-
File.rename("#{@filename}", age_file)
-
@dev = create_logfile(@filename)
-
return true
-
end
-
-
1
def previous_period_end(now)
-
case @shift_age
-
when /^daily$/
-
eod(now - 1 * SiD)
-
when /^weekly$/
-
eod(now - ((now.wday + 1) * SiD))
-
when /^monthly$/
-
eod(now - now.mday * SiD)
-
else
-
now
-
end
-
end
-
-
1
def eod(t)
-
Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
-
end
-
end
-
-
-
#
-
# == Description
-
#
-
# Application -- Add logging support to your application.
-
#
-
# == Usage
-
#
-
# 1. Define your application class as a sub-class of this class.
-
# 2. Override 'run' method in your class to do many things.
-
# 3. Instantiate it and invoke 'start'.
-
#
-
# == Example
-
#
-
# class FooApp < Application
-
# def initialize(foo_app, application_specific, arguments)
-
# super('FooApp') # Name of the application.
-
# end
-
#
-
# def run
-
# ...
-
# log(WARN, 'warning', 'my_method1')
-
# ...
-
# @log.error('my_method2') { 'Error!' }
-
# ...
-
# end
-
# end
-
#
-
# status = FooApp.new(....).start
-
#
-
1
class Application
-
1
include Logger::Severity
-
-
# Name of the application given at initialize.
-
1
attr_reader :appname
-
-
#
-
# == Synopsis
-
#
-
# Application.new(appname = '')
-
#
-
# == Args
-
#
-
# +appname+:: Name of the application.
-
#
-
# == Description
-
#
-
# Create an instance. Log device is +STDERR+ by default. This can be
-
# changed with #set_log.
-
#
-
1
def initialize(appname = nil)
-
@appname = appname
-
@log = Logger.new(STDERR)
-
@log.progname = @appname
-
@level = @log.level
-
end
-
-
#
-
# Start the application. Return the status code.
-
#
-
1
def start
-
status = -1
-
begin
-
log(INFO, "Start of #{ @appname }.")
-
status = run
-
rescue
-
log(FATAL, "Detected an exception. Stopping ... #{$!} (#{$!.class})\n" << $@.join("\n"))
-
ensure
-
log(INFO, "End of #{ @appname }. (status: #{ status.to_s })")
-
end
-
status
-
end
-
-
# Logger for this application. See the class Logger for an explanation.
-
1
def logger
-
@log
-
end
-
-
#
-
# Sets the logger for this application. See the class Logger for an
-
# explanation.
-
#
-
1
def logger=(logger)
-
@log = logger
-
@log.progname = @appname
-
@log.level = @level
-
end
-
-
#
-
# Sets the log device for this application. See <tt>Logger.new</tt> for
-
# an explanation of the arguments.
-
#
-
1
def set_log(logdev, shift_age = 0, shift_size = 1024000)
-
@log = Logger.new(logdev, shift_age, shift_size)
-
@log.progname = @appname
-
@log.level = @level
-
end
-
-
1
def log=(logdev)
-
set_log(logdev)
-
end
-
-
#
-
# Set the logging threshold, just like <tt>Logger#level=</tt>.
-
#
-
1
def level=(level)
-
@level = level
-
@log.level = @level
-
end
-
-
#
-
# See Logger#add. This application's +appname+ is used.
-
#
-
1
def log(severity, message = nil, &block)
-
@log.add(severity, message, @appname, &block) if @log
-
end
-
-
1
private
-
-
1
def run
-
# TODO: should be an NotImplementedError
-
raise RuntimeError.new('Method run must be defined in the derived class.')
-
end
-
end
-
end
-
#
-
# mutex_m.rb -
-
# $Release Version: 3.0$
-
# $Revision: 1.7 $
-
# Original from mutex.rb
-
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
-
# modified by matz
-
# patched by akira yamada
-
#
-
# --
-
# Usage:
-
# require "mutex_m.rb"
-
# obj = Object.new
-
# obj.extend Mutex_m
-
# ...
-
# extended object can be handled like Mutex
-
# or
-
# class Foo
-
# include Mutex_m
-
# ...
-
# end
-
# obj = Foo.new
-
# this obj can be handled like Mutex
-
#
-
-
1
require 'thread'
-
-
1
module Mutex_m
-
1
def Mutex_m.define_aliases(cl)
-
4
cl.module_eval %q{
-
alias locked? mu_locked?
-
alias lock mu_lock
-
alias unlock mu_unlock
-
alias try_lock mu_try_lock
-
alias synchronize mu_synchronize
-
}
-
end
-
-
1
def Mutex_m.append_features(cl)
-
2
super
-
2
define_aliases(cl) unless cl.instance_of?(Module)
-
end
-
-
1
def Mutex_m.extend_object(obj)
-
2
super
-
2
obj.mu_extended
-
end
-
-
1
def mu_extended
-
unless (defined? locked? and
-
2
defined? lock and
-
defined? unlock and
-
defined? try_lock and
-
defined? synchronize)
-
2
Mutex_m.define_aliases(singleton_class)
-
end
-
2
mu_initialize
-
end
-
-
# locking
-
1
def mu_synchronize(&block)
-
28423
@_mutex.synchronize(&block)
-
end
-
-
1
def mu_locked?
-
@_mutex.locked?
-
end
-
-
1
def mu_try_lock
-
@_mutex.try_lock
-
end
-
-
1
def mu_lock
-
@_mutex.lock
-
end
-
-
1
def mu_unlock
-
@_mutex.unlock
-
end
-
-
1
def sleep(timeout = nil)
-
@_mutex.sleep(timeout)
-
end
-
-
1
private
-
-
1
def mu_initialize
-
798
@_mutex = Mutex.new
-
end
-
-
1
def initialize(*args)
-
796
mu_initialize
-
796
super
-
end
-
end
-
#
-
# = net/protocol.rb
-
#
-
#--
-
# Copyright (c) 1999-2004 Yukihiro Matsumoto
-
# Copyright (c) 1999-2004 Minero Aoki
-
#
-
# written and maintained by Minero Aoki <aamine@loveruby.net>
-
#
-
# This program is free software. You can re-distribute and/or
-
# modify this program under the same terms as Ruby itself,
-
# Ruby Distribute License or GNU General Public License.
-
#
-
# $Id: protocol.rb 31860 2011-05-31 08:10:42Z nahi $
-
#++
-
#
-
# WARNING: This file is going to remove.
-
# Do not rely on the implementation written in this file.
-
#
-
-
1
require 'socket'
-
1
require 'timeout'
-
-
1
module Net # :nodoc:
-
-
1
class Protocol #:nodoc: internal use only
-
1
private
-
1
def Protocol.protocol_param(name, val)
-
module_eval(<<-End, __FILE__, __LINE__ + 1)
-
def #{name}
-
#{val}
-
end
-
End
-
end
-
end
-
-
-
1
class ProtocolError < StandardError; end
-
1
class ProtoSyntaxError < ProtocolError; end
-
1
class ProtoFatalError < ProtocolError; end
-
1
class ProtoUnknownError < ProtocolError; end
-
1
class ProtoServerError < ProtocolError; end
-
1
class ProtoAuthError < ProtocolError; end
-
1
class ProtoCommandError < ProtocolError; end
-
1
class ProtoRetriableError < ProtocolError; end
-
1
ProtocRetryError = ProtoRetriableError
-
-
-
1
class BufferedIO #:nodoc: internal use only
-
1
def initialize(io)
-
@io = io
-
@read_timeout = 60
-
@continue_timeout = nil
-
@debug_output = nil
-
@rbuf = ''
-
end
-
-
1
attr_reader :io
-
1
attr_accessor :read_timeout
-
1
attr_accessor :continue_timeout
-
1
attr_accessor :debug_output
-
-
1
def inspect
-
"#<#{self.class} io=#{@io}>"
-
end
-
-
1
def eof?
-
@io.eof?
-
end
-
-
1
def closed?
-
@io.closed?
-
end
-
-
1
def close
-
@io.close
-
end
-
-
#
-
# Read
-
#
-
-
1
public
-
-
1
def read(len, dest = '', ignore_eof = false)
-
LOG "reading #{len} bytes..."
-
read_bytes = 0
-
begin
-
while read_bytes + @rbuf.size < len
-
dest << (s = rbuf_consume(@rbuf.size))
-
read_bytes += s.size
-
rbuf_fill
-
end
-
dest << (s = rbuf_consume(len - read_bytes))
-
read_bytes += s.size
-
rescue EOFError
-
raise unless ignore_eof
-
end
-
LOG "read #{read_bytes} bytes"
-
dest
-
end
-
-
1
def read_all(dest = '')
-
LOG 'reading all...'
-
read_bytes = 0
-
begin
-
while true
-
dest << (s = rbuf_consume(@rbuf.size))
-
read_bytes += s.size
-
rbuf_fill
-
end
-
rescue EOFError
-
;
-
end
-
LOG "read #{read_bytes} bytes"
-
dest
-
end
-
-
1
def readuntil(terminator, ignore_eof = false)
-
begin
-
until idx = @rbuf.index(terminator)
-
rbuf_fill
-
end
-
return rbuf_consume(idx + terminator.size)
-
rescue EOFError
-
raise unless ignore_eof
-
return rbuf_consume(@rbuf.size)
-
end
-
end
-
-
1
def readline
-
readuntil("\n").chop
-
end
-
-
1
private
-
-
1
BUFSIZE = 1024 * 16
-
-
1
def rbuf_fill
-
begin
-
@rbuf << @io.read_nonblock(BUFSIZE)
-
rescue IO::WaitReadable
-
if IO.select([@io], nil, nil, @read_timeout)
-
retry
-
else
-
raise Timeout::Error
-
end
-
rescue IO::WaitWritable
-
# OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
-
# http://www.openssl.org/support/faq.html#PROG10
-
if IO.select(nil, [@io], nil, @read_timeout)
-
retry
-
else
-
raise Timeout::Error
-
end
-
end
-
end
-
-
1
def rbuf_consume(len)
-
s = @rbuf.slice!(0, len)
-
@debug_output << %Q[-> #{s.dump}\n] if @debug_output
-
s
-
end
-
-
#
-
# Write
-
#
-
-
1
public
-
-
1
def write(str)
-
writing {
-
write0 str
-
}
-
end
-
-
1
alias << write
-
-
1
def writeline(str)
-
writing {
-
write0 str + "\r\n"
-
}
-
end
-
-
1
private
-
-
1
def writing
-
@written_bytes = 0
-
@debug_output << '<- ' if @debug_output
-
yield
-
@debug_output << "\n" if @debug_output
-
bytes = @written_bytes
-
@written_bytes = nil
-
bytes
-
end
-
-
1
def write0(str)
-
@debug_output << str.dump if @debug_output
-
len = @io.write(str)
-
@written_bytes += len
-
len
-
end
-
-
#
-
# Logging
-
#
-
-
1
private
-
-
1
def LOG_off
-
@save_debug_out = @debug_output
-
@debug_output = nil
-
end
-
-
1
def LOG_on
-
@debug_output = @save_debug_out
-
end
-
-
1
def LOG(msg)
-
return unless @debug_output
-
@debug_output << msg + "\n"
-
end
-
end
-
-
-
1
class InternetMessageIO < BufferedIO #:nodoc: internal use only
-
1
def initialize(io)
-
super
-
@wbuf = nil
-
end
-
-
#
-
# Read
-
#
-
-
1
def each_message_chunk
-
LOG 'reading message...'
-
LOG_off()
-
read_bytes = 0
-
while (line = readuntil("\r\n")) != ".\r\n"
-
read_bytes += line.size
-
yield line.sub(/\A\./, '')
-
end
-
LOG_on()
-
LOG "read message (#{read_bytes} bytes)"
-
end
-
-
# *library private* (cannot handle 'break')
-
1
def each_list_item
-
while (str = readuntil("\r\n")) != ".\r\n"
-
yield str.chop
-
end
-
end
-
-
1
def write_message_0(src)
-
prev = @written_bytes
-
each_crlf_line(src) do |line|
-
write0 line.sub(/\A\./, '..')
-
end
-
@written_bytes - prev
-
end
-
-
#
-
# Write
-
#
-
-
1
def write_message(src)
-
LOG "writing message from #{src.class}"
-
LOG_off()
-
len = writing {
-
using_each_crlf_line {
-
write_message_0 src
-
}
-
}
-
LOG_on()
-
LOG "wrote #{len} bytes"
-
len
-
end
-
-
1
def write_message_by_block(&block)
-
LOG 'writing message from block'
-
LOG_off()
-
len = writing {
-
using_each_crlf_line {
-
begin
-
block.call(WriteAdapter.new(self, :write_message_0))
-
rescue LocalJumpError
-
# allow `break' from writer block
-
end
-
}
-
}
-
LOG_on()
-
LOG "wrote #{len} bytes"
-
len
-
end
-
-
1
private
-
-
1
def using_each_crlf_line
-
@wbuf = ''
-
yield
-
if not @wbuf.empty? # unterminated last line
-
write0 @wbuf.chomp + "\r\n"
-
elsif @written_bytes == 0 # empty src
-
write0 "\r\n"
-
end
-
write0 ".\r\n"
-
@wbuf = nil
-
end
-
-
1
def each_crlf_line(src)
-
buffer_filling(@wbuf, src) do
-
while line = @wbuf.slice!(/\A.*(?:\n|\r\n|\r(?!\z))/n)
-
yield line.chomp("\n") + "\r\n"
-
end
-
end
-
end
-
-
1
def buffer_filling(buf, src)
-
case src
-
when String # for speeding up.
-
0.step(src.size - 1, 1024) do |i|
-
buf << src[i, 1024]
-
yield
-
end
-
when File # for speeding up.
-
while s = src.read(1024)
-
buf << s
-
yield
-
end
-
else # generic reader
-
src.each do |str|
-
buf << str
-
yield if buf.size > 1024
-
end
-
yield unless buf.empty?
-
end
-
end
-
end
-
-
-
#
-
# The writer adapter class
-
#
-
1
class WriteAdapter
-
1
def initialize(socket, method)
-
@socket = socket
-
@method_id = method
-
end
-
-
1
def inspect
-
"#<#{self.class} socket=#{@socket.inspect}>"
-
end
-
-
1
def write(str)
-
@socket.__send__(@method_id, str)
-
end
-
-
1
alias print write
-
-
1
def <<(str)
-
write str
-
self
-
end
-
-
1
def puts(str = '')
-
write str.chomp("\n") + "\n"
-
end
-
-
1
def printf(*args)
-
write sprintf(*args)
-
end
-
end
-
-
-
1
class ReadAdapter #:nodoc: internal use only
-
1
def initialize(block)
-
@block = block
-
end
-
-
1
def inspect
-
"#<#{self.class}>"
-
end
-
-
1
def <<(str)
-
call_block(str, &@block) if @block
-
end
-
-
1
private
-
-
# This method is needed because @block must be called by yield,
-
# not Proc#call. You can see difference when using `break' in
-
# the block.
-
1
def call_block(str)
-
yield str
-
end
-
end
-
-
-
1
module NetPrivate #:nodoc: obsolete
-
1
Socket = ::Net::InternetMessageIO
-
end
-
-
end # module Net
-
# = net/smtp.rb
-
#
-
# Copyright (c) 1999-2007 Yukihiro Matsumoto.
-
#
-
# Copyright (c) 1999-2007 Minero Aoki.
-
#
-
# Written & maintained by Minero Aoki <aamine@loveruby.net>.
-
#
-
# Documented by William Webber and Minero Aoki.
-
#
-
# This program is free software. You can re-distribute and/or
-
# modify this program under the same terms as Ruby itself.
-
#
-
# NOTE: You can find Japanese version of this document at:
-
# http://www.ruby-lang.org/ja/man/html/net_smtp.html
-
#
-
# $Id: smtp.rb 31710 2011-05-23 00:21:10Z drbrain $
-
#
-
# See Net::SMTP for documentation.
-
#
-
-
1
require 'net/protocol'
-
1
require 'digest/md5'
-
1
require 'timeout'
-
1
begin
-
1
require 'openssl'
-
rescue LoadError
-
end
-
-
1
module Net
-
-
# Module mixed in to all SMTP error classes
-
1
module SMTPError
-
# This *class* is a module for backward compatibility.
-
# In later release, this module becomes a class.
-
end
-
-
# Represents an SMTP authentication error.
-
1
class SMTPAuthenticationError < ProtoAuthError
-
1
include SMTPError
-
end
-
-
# Represents SMTP error code 420 or 450, a temporary error.
-
1
class SMTPServerBusy < ProtoServerError
-
1
include SMTPError
-
end
-
-
# Represents an SMTP command syntax error (error code 500)
-
1
class SMTPSyntaxError < ProtoSyntaxError
-
1
include SMTPError
-
end
-
-
# Represents a fatal SMTP error (error code 5xx, except for 500)
-
1
class SMTPFatalError < ProtoFatalError
-
1
include SMTPError
-
end
-
-
# Unexpected reply code returned from server.
-
1
class SMTPUnknownError < ProtoUnknownError
-
1
include SMTPError
-
end
-
-
# Command is not supported on server.
-
1
class SMTPUnsupportedCommand < ProtocolError
-
1
include SMTPError
-
end
-
-
#
-
# = Net::SMTP
-
#
-
# == What is This Library?
-
#
-
# This library provides functionality to send internet
-
# mail via SMTP, the Simple Mail Transfer Protocol. For details of
-
# SMTP itself, see [RFC2821] (http://www.ietf.org/rfc/rfc2821.txt).
-
#
-
# == What is This Library NOT?
-
#
-
# This library does NOT provide functions to compose internet mails.
-
# You must create them by yourself. If you want better mail support,
-
# try RubyMail or TMail. You can get both libraries from RAA.
-
# (http://www.ruby-lang.org/en/raa.html)
-
#
-
# FYI: the official documentation on internet mail is: [RFC2822] (http://www.ietf.org/rfc/rfc2822.txt).
-
#
-
# == Examples
-
#
-
# === Sending Messages
-
#
-
# You must open a connection to an SMTP server before sending messages.
-
# The first argument is the address of your SMTP server, and the second
-
# argument is the port number. Using SMTP.start with a block is the simplest
-
# way to do this. This way, the SMTP connection is closed automatically
-
# after the block is executed.
-
#
-
# require 'net/smtp'
-
# Net::SMTP.start('your.smtp.server', 25) do |smtp|
-
# # Use the SMTP object smtp only in this block.
-
# end
-
#
-
# Replace 'your.smtp.server' with your SMTP server. Normally
-
# your system manager or internet provider supplies a server
-
# for you.
-
#
-
# Then you can send messages.
-
#
-
# msgstr = <<END_OF_MESSAGE
-
# From: Your Name <your@mail.address>
-
# To: Destination Address <someone@example.com>
-
# Subject: test message
-
# Date: Sat, 23 Jun 2001 16:26:43 +0900
-
# Message-Id: <unique.message.id.string@example.com>
-
#
-
# This is a test message.
-
# END_OF_MESSAGE
-
#
-
# require 'net/smtp'
-
# Net::SMTP.start('your.smtp.server', 25) do |smtp|
-
# smtp.send_message msgstr,
-
# 'your@mail.address',
-
# 'his_address@example.com'
-
# end
-
#
-
# === Closing the Session
-
#
-
# You MUST close the SMTP session after sending messages, by calling
-
# the #finish method:
-
#
-
# # using SMTP#finish
-
# smtp = Net::SMTP.start('your.smtp.server', 25)
-
# smtp.send_message msgstr, 'from@address', 'to@address'
-
# smtp.finish
-
#
-
# You can also use the block form of SMTP.start/SMTP#start. This closes
-
# the SMTP session automatically:
-
#
-
# # using block form of SMTP.start
-
# Net::SMTP.start('your.smtp.server', 25) do |smtp|
-
# smtp.send_message msgstr, 'from@address', 'to@address'
-
# end
-
#
-
# I strongly recommend this scheme. This form is simpler and more robust.
-
#
-
# === HELO domain
-
#
-
# In almost all situations, you must provide a third argument
-
# to SMTP.start/SMTP#start. This is the domain name which you are on
-
# (the host to send mail from). It is called the "HELO domain".
-
# The SMTP server will judge whether it should send or reject
-
# the SMTP session by inspecting the HELO domain.
-
#
-
# Net::SMTP.start('your.smtp.server', 25,
-
# 'mail.from.domain') { |smtp| ... }
-
#
-
# === SMTP Authentication
-
#
-
# The Net::SMTP class supports three authentication schemes;
-
# PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554])
-
# To use SMTP authentication, pass extra arguments to
-
# SMTP.start/SMTP#start.
-
#
-
# # PLAIN
-
# Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
-
# 'Your Account', 'Your Password', :plain)
-
# # LOGIN
-
# Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
-
# 'Your Account', 'Your Password', :login)
-
#
-
# # CRAM MD5
-
# Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
-
# 'Your Account', 'Your Password', :cram_md5)
-
#
-
1
class SMTP
-
-
1
Revision = %q$Revision: 31710 $.split[1]
-
-
# The default SMTP port number, 25.
-
1
def SMTP.default_port
-
25
-
end
-
-
# The default mail submission port number, 587.
-
1
def SMTP.default_submission_port
-
587
-
end
-
-
# The default SMTPS port number, 465.
-
1
def SMTP.default_tls_port
-
465
-
end
-
-
1
class << self
-
1
alias default_ssl_port default_tls_port
-
end
-
-
1
def SMTP.default_ssl_context
-
OpenSSL::SSL::SSLContext.new
-
end
-
-
#
-
# Creates a new Net::SMTP object.
-
#
-
# +address+ is the hostname or ip address of your SMTP
-
# server. +port+ is the port to connect to; it defaults to
-
# port 25.
-
#
-
# This method does not open the TCP connection. You can use
-
# SMTP.start instead of SMTP.new if you want to do everything
-
# at once. Otherwise, follow SMTP.new with SMTP#start.
-
#
-
1
def initialize(address, port = nil)
-
@address = address
-
@port = (port || SMTP.default_port)
-
@esmtp = true
-
@capabilities = nil
-
@socket = nil
-
@started = false
-
@open_timeout = 30
-
@read_timeout = 60
-
@error_occured = false
-
@debug_output = nil
-
@tls = false
-
@starttls = false
-
@ssl_context = nil
-
end
-
-
# Provide human-readable stringification of class state.
-
1
def inspect
-
"#<#{self.class} #{@address}:#{@port} started=#{@started}>"
-
end
-
-
#
-
# Set whether to use ESMTP or not. This should be done before
-
# calling #start. Note that if #start is called in ESMTP mode,
-
# and the connection fails due to a ProtocolError, the SMTP
-
# object will automatically switch to plain SMTP mode and
-
# retry (but not vice versa).
-
#
-
1
attr_accessor :esmtp
-
-
# +true+ if the SMTP object uses ESMTP (which it does by default).
-
1
alias :esmtp? :esmtp
-
-
# true if server advertises STARTTLS.
-
# You cannot get valid value before opening SMTP session.
-
1
def capable_starttls?
-
capable?('STARTTLS')
-
end
-
-
1
def capable?(key)
-
return nil unless @capabilities
-
@capabilities[key] ? true : false
-
end
-
1
private :capable?
-
-
# true if server advertises AUTH PLAIN.
-
# You cannot get valid value before opening SMTP session.
-
1
def capable_plain_auth?
-
auth_capable?('PLAIN')
-
end
-
-
# true if server advertises AUTH LOGIN.
-
# You cannot get valid value before opening SMTP session.
-
1
def capable_login_auth?
-
auth_capable?('LOGIN')
-
end
-
-
# true if server advertises AUTH CRAM-MD5.
-
# You cannot get valid value before opening SMTP session.
-
1
def capable_cram_md5_auth?
-
auth_capable?('CRAM-MD5')
-
end
-
-
1
def auth_capable?(type)
-
return nil unless @capabilities
-
return false unless @capabilities['AUTH']
-
@capabilities['AUTH'].include?(type)
-
end
-
1
private :auth_capable?
-
-
# Returns supported authentication methods on this server.
-
# You cannot get valid value before opening SMTP session.
-
1
def capable_auth_types
-
return [] unless @capabilities
-
return [] unless @capabilities['AUTH']
-
@capabilities['AUTH']
-
end
-
-
# true if this object uses SMTP/TLS (SMTPS).
-
1
def tls?
-
@tls
-
end
-
-
1
alias ssl? tls?
-
-
# Enables SMTP/TLS (SMTPS: SMTP over direct TLS connection) for
-
# this object. Must be called before the connection is established
-
# to have any effect. +context+ is a OpenSSL::SSL::SSLContext object.
-
1
def enable_tls(context = SMTP.default_ssl_context)
-
raise 'openssl library not installed' unless defined?(OpenSSL)
-
raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls
-
@tls = true
-
@ssl_context = context
-
end
-
-
1
alias enable_ssl enable_tls
-
-
# Disables SMTP/TLS for this object. Must be called before the
-
# connection is established to have any effect.
-
1
def disable_tls
-
@tls = false
-
@ssl_context = nil
-
end
-
-
1
alias disable_ssl disable_tls
-
-
# Returns truth value if this object uses STARTTLS.
-
# If this object always uses STARTTLS, returns :always.
-
# If this object uses STARTTLS when the server support TLS, returns :auto.
-
1
def starttls?
-
@starttls
-
end
-
-
# true if this object uses STARTTLS.
-
1
def starttls_always?
-
@starttls == :always
-
end
-
-
# true if this object uses STARTTLS when server advertises STARTTLS.
-
1
def starttls_auto?
-
@starttls == :auto
-
end
-
-
# Enables SMTP/TLS (STARTTLS) for this object.
-
# +context+ is a OpenSSL::SSL::SSLContext object.
-
1
def enable_starttls(context = SMTP.default_ssl_context)
-
raise 'openssl library not installed' unless defined?(OpenSSL)
-
raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
-
@starttls = :always
-
@ssl_context = context
-
end
-
-
# Enables SMTP/TLS (STARTTLS) for this object if server accepts.
-
# +context+ is a OpenSSL::SSL::SSLContext object.
-
1
def enable_starttls_auto(context = SMTP.default_ssl_context)
-
raise 'openssl library not installed' unless defined?(OpenSSL)
-
raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls
-
@starttls = :auto
-
@ssl_context = context
-
end
-
-
# Disables SMTP/TLS (STARTTLS) for this object. Must be called
-
# before the connection is established to have any effect.
-
1
def disable_starttls
-
@starttls = false
-
@ssl_context = nil
-
end
-
-
# The address of the SMTP server to connect to.
-
1
attr_reader :address
-
-
# The port number of the SMTP server to connect to.
-
1
attr_reader :port
-
-
# Seconds to wait while attempting to open a connection.
-
# If the connection cannot be opened within this time, a
-
# TimeoutError is raised.
-
1
attr_accessor :open_timeout
-
-
# Seconds to wait while reading one block (by one read(2) call).
-
# If the read(2) call does not complete within this time, a
-
# TimeoutError is raised.
-
1
attr_reader :read_timeout
-
-
# Set the number of seconds to wait until timing-out a read(2)
-
# call.
-
1
def read_timeout=(sec)
-
@socket.read_timeout = sec if @socket
-
@read_timeout = sec
-
end
-
-
#
-
# WARNING: This method causes serious security holes.
-
# Use this method for only debugging.
-
#
-
# Set an output stream for debug logging.
-
# You must call this before #start.
-
#
-
# # example
-
# smtp = Net::SMTP.new(addr, port)
-
# smtp.set_debug_output $stderr
-
# smtp.start do |smtp|
-
# ....
-
# end
-
#
-
1
def debug_output=(arg)
-
@debug_output = arg
-
end
-
-
1
alias set_debug_output debug_output=
-
-
#
-
# SMTP session control
-
#
-
-
#
-
# Creates a new Net::SMTP object and connects to the server.
-
#
-
# This method is equivalent to:
-
#
-
# Net::SMTP.new(address, port).start(helo_domain, account, password, authtype)
-
#
-
# === Example
-
#
-
# Net::SMTP.start('your.smtp.server') do |smtp|
-
# smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
-
# end
-
#
-
# === Block Usage
-
#
-
# If called with a block, the newly-opened Net::SMTP object is yielded
-
# to the block, and automatically closed when the block finishes. If called
-
# without a block, the newly-opened Net::SMTP object is returned to
-
# the caller, and it is the caller's responsibility to close it when
-
# finished.
-
#
-
# === Parameters
-
#
-
# +address+ is the hostname or ip address of your smtp server.
-
#
-
# +port+ is the port to connect to; it defaults to port 25.
-
#
-
# +helo+ is the _HELO_ _domain_ provided by the client to the
-
# server (see overview comments); it defaults to 'localhost'.
-
#
-
# The remaining arguments are used for SMTP authentication, if required
-
# or desired. +user+ is the account name; +secret+ is your password
-
# or other authentication token; and +authtype+ is the authentication
-
# type, one of :plain, :login, or :cram_md5. See the discussion of
-
# SMTP Authentication in the overview notes.
-
#
-
# === Errors
-
#
-
# This method may raise:
-
#
-
# * Net::SMTPAuthenticationError
-
# * Net::SMTPServerBusy
-
# * Net::SMTPSyntaxError
-
# * Net::SMTPFatalError
-
# * Net::SMTPUnknownError
-
# * IOError
-
# * TimeoutError
-
#
-
1
def SMTP.start(address, port = nil, helo = 'localhost',
-
user = nil, secret = nil, authtype = nil,
-
&block) # :yield: smtp
-
new(address, port).start(helo, user, secret, authtype, &block)
-
end
-
-
# +true+ if the SMTP session has been started.
-
1
def started?
-
@started
-
end
-
-
#
-
# Opens a TCP connection and starts the SMTP session.
-
#
-
# === Parameters
-
#
-
# +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
-
# the discussion in the overview notes.
-
#
-
# If both of +user+ and +secret+ are given, SMTP authentication
-
# will be attempted using the AUTH command. +authtype+ specifies
-
# the type of authentication to attempt; it must be one of
-
# :login, :plain, and :cram_md5. See the notes on SMTP Authentication
-
# in the overview.
-
#
-
# === Block Usage
-
#
-
# When this methods is called with a block, the newly-started SMTP
-
# object is yielded to the block, and automatically closed after
-
# the block call finishes. Otherwise, it is the caller's
-
# responsibility to close the session when finished.
-
#
-
# === Example
-
#
-
# This is very similar to the class method SMTP.start.
-
#
-
# require 'net/smtp'
-
# smtp = Net::SMTP.new('smtp.mail.server', 25)
-
# smtp.start(helo_domain, account, password, authtype) do |smtp|
-
# smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
-
# end
-
#
-
# The primary use of this method (as opposed to SMTP.start)
-
# is probably to set debugging (#set_debug_output) or ESMTP
-
# (#esmtp=), which must be done before the session is
-
# started.
-
#
-
# === Errors
-
#
-
# If session has already been started, an IOError will be raised.
-
#
-
# This method may raise:
-
#
-
# * Net::SMTPAuthenticationError
-
# * Net::SMTPServerBusy
-
# * Net::SMTPSyntaxError
-
# * Net::SMTPFatalError
-
# * Net::SMTPUnknownError
-
# * IOError
-
# * TimeoutError
-
#
-
1
def start(helo = 'localhost',
-
user = nil, secret = nil, authtype = nil) # :yield: smtp
-
if block_given?
-
begin
-
do_start helo, user, secret, authtype
-
return yield(self)
-
ensure
-
do_finish
-
end
-
else
-
do_start helo, user, secret, authtype
-
return self
-
end
-
end
-
-
# Finishes the SMTP session and closes TCP connection.
-
# Raises IOError if not started.
-
1
def finish
-
raise IOError, 'not yet started' unless started?
-
do_finish
-
end
-
-
1
private
-
-
1
def tcp_socket(address, port)
-
TCPSocket.open address, port
-
end
-
-
1
def do_start(helo_domain, user, secret, authtype)
-
raise IOError, 'SMTP session already started' if @started
-
if user or secret
-
check_auth_method(authtype || DEFAULT_AUTH_TYPE)
-
check_auth_args user, secret
-
end
-
s = timeout(@open_timeout) { tcp_socket(@address, @port) }
-
logging "Connection opened: #{@address}:#{@port}"
-
@socket = new_internet_message_io(tls? ? tlsconnect(s) : s)
-
check_response critical { recv_response() }
-
do_helo helo_domain
-
if starttls_always? or (capable_starttls? and starttls_auto?)
-
unless capable_starttls?
-
raise SMTPUnsupportedCommand,
-
"STARTTLS is not supported on this server"
-
end
-
starttls
-
@socket = new_internet_message_io(tlsconnect(s))
-
# helo response may be different after STARTTLS
-
do_helo helo_domain
-
end
-
authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user
-
@started = true
-
ensure
-
unless @started
-
# authentication failed, cancel connection.
-
s.close if s and not s.closed?
-
@socket = nil
-
end
-
end
-
-
1
def ssl_socket(socket, context)
-
OpenSSL::SSL::SSLSocket.new socket, context
-
end
-
-
1
def tlsconnect(s)
-
verified = false
-
s = ssl_socket(s, @ssl_context)
-
logging "TLS connection started"
-
s.sync_close = true
-
s.connect
-
if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
-
s.post_connection_check(@address)
-
end
-
verified = true
-
s
-
ensure
-
s.close unless verified
-
end
-
-
1
def new_internet_message_io(s)
-
io = InternetMessageIO.new(s)
-
io.read_timeout = @read_timeout
-
io.debug_output = @debug_output
-
io
-
end
-
-
1
def do_helo(helo_domain)
-
res = @esmtp ? ehlo(helo_domain) : helo(helo_domain)
-
@capabilities = res.capabilities
-
rescue SMTPError
-
if @esmtp
-
@esmtp = false
-
@error_occured = false
-
retry
-
end
-
raise
-
end
-
-
1
def do_finish
-
quit if @socket and not @socket.closed? and not @error_occured
-
ensure
-
@started = false
-
@error_occured = false
-
@socket.close if @socket and not @socket.closed?
-
@socket = nil
-
end
-
-
#
-
# Message Sending
-
#
-
-
1
public
-
-
#
-
# Sends +msgstr+ as a message. Single CR ("\r") and LF ("\n") found
-
# in the +msgstr+, are converted into the CR LF pair. You cannot send a
-
# binary message with this method. +msgstr+ should include both
-
# the message headers and body.
-
#
-
# +from_addr+ is a String representing the source mail address.
-
#
-
# +to_addr+ is a String or Strings or Array of Strings, representing
-
# the destination mail address or addresses.
-
#
-
# === Example
-
#
-
# Net::SMTP.start('smtp.example.com') do |smtp|
-
# smtp.send_message msgstr,
-
# 'from@example.com',
-
# ['dest@example.com', 'dest2@example.com']
-
# end
-
#
-
# === Errors
-
#
-
# This method may raise:
-
#
-
# * Net::SMTPServerBusy
-
# * Net::SMTPSyntaxError
-
# * Net::SMTPFatalError
-
# * Net::SMTPUnknownError
-
# * IOError
-
# * TimeoutError
-
#
-
1
def send_message(msgstr, from_addr, *to_addrs)
-
raise IOError, 'closed session' unless @socket
-
mailfrom from_addr
-
rcptto_list(to_addrs) {data msgstr}
-
end
-
-
1
alias send_mail send_message
-
1
alias sendmail send_message # obsolete
-
-
#
-
# Opens a message writer stream and gives it to the block.
-
# The stream is valid only in the block, and has these methods:
-
#
-
# puts(str = ''):: outputs STR and CR LF.
-
# print(str):: outputs STR.
-
# printf(fmt, *args):: outputs sprintf(fmt,*args).
-
# write(str):: outputs STR and returns the length of written bytes.
-
# <<(str):: outputs STR and returns self.
-
#
-
# If a single CR ("\r") or LF ("\n") is found in the message,
-
# it is converted to the CR LF pair. You cannot send a binary
-
# message with this method.
-
#
-
# === Parameters
-
#
-
# +from_addr+ is a String representing the source mail address.
-
#
-
# +to_addr+ is a String or Strings or Array of Strings, representing
-
# the destination mail address or addresses.
-
#
-
# === Example
-
#
-
# Net::SMTP.start('smtp.example.com', 25) do |smtp|
-
# smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f|
-
# f.puts 'From: from@example.com'
-
# f.puts 'To: dest@example.com'
-
# f.puts 'Subject: test message'
-
# f.puts
-
# f.puts 'This is a test message.'
-
# end
-
# end
-
#
-
# === Errors
-
#
-
# This method may raise:
-
#
-
# * Net::SMTPServerBusy
-
# * Net::SMTPSyntaxError
-
# * Net::SMTPFatalError
-
# * Net::SMTPUnknownError
-
# * IOError
-
# * TimeoutError
-
#
-
1
def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream
-
raise IOError, 'closed session' unless @socket
-
mailfrom from_addr
-
rcptto_list(to_addrs) {data(&block)}
-
end
-
-
1
alias ready open_message_stream # obsolete
-
-
#
-
# Authentication
-
#
-
-
1
public
-
-
1
DEFAULT_AUTH_TYPE = :plain
-
-
1
def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE)
-
check_auth_method authtype
-
check_auth_args user, secret
-
send auth_method(authtype), user, secret
-
end
-
-
1
def auth_plain(user, secret)
-
check_auth_args user, secret
-
res = critical {
-
get_response('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}"))
-
}
-
check_auth_response res
-
res
-
end
-
-
1
def auth_login(user, secret)
-
check_auth_args user, secret
-
res = critical {
-
check_auth_continue get_response('AUTH LOGIN')
-
check_auth_continue get_response(base64_encode(user))
-
get_response(base64_encode(secret))
-
}
-
check_auth_response res
-
res
-
end
-
-
1
def auth_cram_md5(user, secret)
-
check_auth_args user, secret
-
res = critical {
-
res0 = get_response('AUTH CRAM-MD5')
-
check_auth_continue res0
-
crammed = cram_md5_response(secret, res0.cram_md5_challenge)
-
get_response(base64_encode("#{user} #{crammed}"))
-
}
-
check_auth_response res
-
res
-
end
-
-
1
private
-
-
1
def check_auth_method(type)
-
unless respond_to?(auth_method(type), true)
-
raise ArgumentError, "wrong authentication type #{type}"
-
end
-
end
-
-
1
def auth_method(type)
-
"auth_#{type.to_s.downcase}".intern
-
end
-
-
1
def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE)
-
unless user
-
raise ArgumentError, 'SMTP-AUTH requested but missing user name'
-
end
-
unless secret
-
raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase'
-
end
-
end
-
-
1
def base64_encode(str)
-
# expects "str" may not become too long
-
[str].pack('m').gsub(/\s+/, '')
-
end
-
-
1
IMASK = 0x36
-
1
OMASK = 0x5c
-
-
# CRAM-MD5: [RFC2195]
-
1
def cram_md5_response(secret, challenge)
-
tmp = Digest::MD5.digest(cram_secret(secret, IMASK) + challenge)
-
Digest::MD5.hexdigest(cram_secret(secret, OMASK) + tmp)
-
end
-
-
1
CRAM_BUFSIZE = 64
-
-
1
def cram_secret(secret, mask)
-
secret = Digest::MD5.digest(secret) if secret.size > CRAM_BUFSIZE
-
buf = secret.ljust(CRAM_BUFSIZE, "\0")
-
0.upto(buf.size - 1) do |i|
-
buf[i] = (buf[i].ord ^ mask).chr
-
end
-
buf
-
end
-
-
#
-
# SMTP command dispatcher
-
#
-
-
1
public
-
-
1
def starttls
-
getok('STARTTLS')
-
end
-
-
1
def helo(domain)
-
getok("HELO #{domain}")
-
end
-
-
1
def ehlo(domain)
-
getok("EHLO #{domain}")
-
end
-
-
1
def mailfrom(from_addr)
-
if $SAFE > 0
-
raise SecurityError, 'tainted from_addr' if from_addr.tainted?
-
end
-
getok("MAIL FROM:<#{from_addr}>")
-
end
-
-
1
def rcptto_list(to_addrs)
-
raise ArgumentError, 'mail destination not given' if to_addrs.empty?
-
ok_users = []
-
unknown_users = []
-
to_addrs.flatten.each do |addr|
-
begin
-
rcptto addr
-
rescue SMTPAuthenticationError
-
unknown_users << addr.dump
-
else
-
ok_users << addr
-
end
-
end
-
raise ArgumentError, 'mail destination not given' if ok_users.empty?
-
ret = yield
-
unless unknown_users.empty?
-
raise SMTPAuthenticationError, "failed to deliver for #{unknown_users.join(', ')}"
-
end
-
ret
-
end
-
-
1
def rcptto(to_addr)
-
if $SAFE > 0
-
raise SecurityError, 'tainted to_addr' if to_addr.tainted?
-
end
-
getok("RCPT TO:<#{to_addr}>")
-
end
-
-
# This method sends a message.
-
# If +msgstr+ is given, sends it as a message.
-
# If block is given, yield a message writer stream.
-
# You must write message before the block is closed.
-
#
-
# # Example 1 (by string)
-
# smtp.data(<<EndMessage)
-
# From: john@example.com
-
# To: betty@example.com
-
# Subject: I found a bug
-
#
-
# Check vm.c:58879.
-
# EndMessage
-
#
-
# # Example 2 (by block)
-
# smtp.data {|f|
-
# f.puts "From: john@example.com"
-
# f.puts "To: betty@example.com"
-
# f.puts "Subject: I found a bug"
-
# f.puts ""
-
# f.puts "Check vm.c:58879."
-
# }
-
#
-
1
def data(msgstr = nil, &block) #:yield: stream
-
if msgstr and block
-
raise ArgumentError, "message and block are exclusive"
-
end
-
unless msgstr or block
-
raise ArgumentError, "message or block is required"
-
end
-
res = critical {
-
check_continue get_response('DATA')
-
if msgstr
-
@socket.write_message msgstr
-
else
-
@socket.write_message_by_block(&block)
-
end
-
recv_response()
-
}
-
check_response res
-
res
-
end
-
-
1
def quit
-
getok('QUIT')
-
end
-
-
1
private
-
-
1
def getok(reqline)
-
res = critical {
-
@socket.writeline reqline
-
recv_response()
-
}
-
check_response res
-
res
-
end
-
-
1
def get_response(reqline)
-
@socket.writeline reqline
-
recv_response()
-
end
-
-
1
def recv_response
-
buf = ''
-
while true
-
line = @socket.readline
-
buf << line << "\n"
-
break unless line[3,1] == '-' # "210-PIPELINING"
-
end
-
Response.parse(buf)
-
end
-
-
1
def critical(&block)
-
return '200 dummy reply code' if @error_occured
-
begin
-
return yield()
-
rescue Exception
-
@error_occured = true
-
raise
-
end
-
end
-
-
1
def check_response(res)
-
unless res.success?
-
raise res.exception_class, res.message
-
end
-
end
-
-
1
def check_continue(res)
-
unless res.continue?
-
raise SMTPUnknownError, "could not get 3xx (#{res.status})"
-
end
-
end
-
-
1
def check_auth_response(res)
-
unless res.success?
-
raise SMTPAuthenticationError, res.message
-
end
-
end
-
-
1
def check_auth_continue(res)
-
unless res.continue?
-
raise res.exception_class, res.message
-
end
-
end
-
-
# This class represents a response received by the SMTP server. Instances
-
# of this class are created by the SMTP class; they should not be directly
-
# created by the user. For more information on SMTP responses, view
-
# {Section 4.2 of RFC 5321}[http://tools.ietf.org/html/rfc5321#section-4.2]
-
1
class Response
-
# Parses the received response and separates the reply code and the human
-
# readable reply text
-
1
def self.parse(str)
-
new(str[0,3], str)
-
end
-
-
# Creates a new instance of the Response class and sets the status and
-
# string attributes
-
1
def initialize(status, string)
-
@status = status
-
@string = string
-
end
-
-
# The three digit reply code of the SMTP response
-
1
attr_reader :status
-
-
# The human readable reply text of the SMTP response
-
1
attr_reader :string
-
-
# Takes the first digit of the reply code to determine the status type
-
1
def status_type_char
-
@status[0, 1]
-
end
-
-
# Determines whether the response received was a Positive Completion
-
# reply (2xx reply code)
-
1
def success?
-
status_type_char() == '2'
-
end
-
-
# Determines whether the response received was a Positive Intermediate
-
# reply (3xx reply code)
-
1
def continue?
-
status_type_char() == '3'
-
end
-
-
# The first line of the human readable reply text
-
1
def message
-
@string.lines.first
-
end
-
-
# Creates a CRAM-MD5 challenge. You can view more information on CRAM-MD5
-
# on Wikipedia: http://en.wikipedia.org/wiki/CRAM-MD5
-
1
def cram_md5_challenge
-
@string.split(/ /)[1].unpack('m')[0]
-
end
-
-
# Returns a hash of the human readable reply text in the response if it
-
# is multiple lines. It does not return the first line. The key of the
-
# hash is the first word the value of the hash is an array with each word
-
# thereafter being a value in the array
-
1
def capabilities
-
return {} unless @string[3, 1] == '-'
-
h = {}
-
@string.lines.drop(1).each do |line|
-
k, *v = line[4..-1].chomp.split
-
h[k] = v
-
end
-
h
-
end
-
-
# Determines whether there was an error and raies the appropriate error
-
# based on the reply code of the response
-
1
def exception_class
-
case @status
-
when /\A4/ then SMTPServerBusy
-
when /\A50/ then SMTPSyntaxError
-
when /\A53/ then SMTPAuthenticationError
-
when /\A5/ then SMTPFatalError
-
else SMTPUnknownError
-
end
-
end
-
end
-
-
1
def logging(msg)
-
@debug_output << msg + "\n" if @debug_output
-
end
-
-
end # class SMTP
-
-
1
SMTPSession = SMTP
-
-
end
-
=begin
-
= $RCSfile$ -- Loader for all OpenSSL C-space and Ruby-space definitions
-
-
= Info
-
'OpenSSL for Ruby 2' project
-
Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
-
All rights reserved.
-
-
= Licence
-
This program is licenced under the same licence as Ruby.
-
(See the file 'LICENCE'.)
-
-
= Version
-
$Id: openssl.rb 32665 2011-07-25 06:38:44Z nahi $
-
=end
-
-
1
require 'openssl.so'
-
-
1
require 'openssl/bn'
-
1
require 'openssl/cipher'
-
1
require 'openssl/config'
-
1
require 'openssl/digest'
-
1
require 'openssl/ssl-internal'
-
1
require 'openssl/x509-internal'
-
-
#--
-
#
-
# $RCSfile$
-
#
-
# = Ruby-space definitions that completes C-space funcs for BN
-
#
-
# = Info
-
# 'OpenSSL for Ruby 2' project
-
# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
-
# All rights reserved.
-
#
-
# = Licence
-
# This program is licenced under the same licence as Ruby.
-
# (See the file 'LICENCE'.)
-
#
-
# = Version
-
# $Id: bn.rb 33067 2011-08-25 00:52:10Z drbrain $
-
#
-
#++
-
-
1
module OpenSSL
-
1
class BN
-
1
include Comparable
-
end # BN
-
end # OpenSSL
-
-
##
-
# Add double dispatch to Integer
-
#
-
1
class Integer
-
1
def to_bn
-
OpenSSL::BN::new(self.to_s(16), 16)
-
end
-
end # Integer
-
-
=begin
-
= $RCSfile$ -- Buffering mix-in module.
-
-
= Info
-
'OpenSSL for Ruby 2' project
-
Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
-
All rights reserved.
-
-
= Licence
-
This program is licenced under the same licence as Ruby.
-
(See the file 'LICENCE'.)
-
-
= Version
-
$Id: buffering.rb 32012 2011-06-11 14:07:42Z nahi $
-
=end
-
-
##
-
# OpenSSL IO buffering mix-in module.
-
#
-
# This module allows an OpenSSL::SSL::SSLSocket to behave like an IO.
-
-
1
module OpenSSL::Buffering
-
1
include Enumerable
-
-
##
-
# The "sync mode" of the SSLSocket.
-
#
-
# See IO#sync for full details.
-
-
1
attr_accessor :sync
-
-
##
-
# Default size to read from or write to the SSLSocket for buffer operations.
-
-
1
BLOCK_SIZE = 1024*16
-
-
1
def initialize(*args)
-
@eof = false
-
@rbuffer = ""
-
@sync = @io.sync
-
end
-
-
#
-
# for reading.
-
#
-
1
private
-
-
##
-
# Fills the buffer from the underlying SSLSocket
-
-
1
def fill_rbuff
-
begin
-
@rbuffer << self.sysread(BLOCK_SIZE)
-
rescue Errno::EAGAIN
-
retry
-
rescue EOFError
-
@eof = true
-
end
-
end
-
-
##
-
# Consumes +size+ bytes from the buffer
-
-
1
def consume_rbuff(size=nil)
-
if @rbuffer.empty?
-
nil
-
else
-
size = @rbuffer.size unless size
-
ret = @rbuffer[0, size]
-
@rbuffer[0, size] = ""
-
ret
-
end
-
end
-
-
1
public
-
-
##
-
# Reads +size+ bytes from the stream. If +buf+ is provided it must
-
# reference a string which will receive the data.
-
#
-
# See IO#read for full details.
-
-
1
def read(size=nil, buf=nil)
-
if size == 0
-
if buf
-
buf.clear
-
return buf
-
else
-
return ""
-
end
-
end
-
until @eof
-
break if size && size <= @rbuffer.size
-
fill_rbuff
-
end
-
ret = consume_rbuff(size) || ""
-
if buf
-
buf.replace(ret)
-
ret = buf
-
end
-
(size && ret.empty?) ? nil : ret
-
end
-
-
##
-
# Reads at most +maxlen+ bytes from the stream. If +buf+ is provided it
-
# must reference a string which will receive the data.
-
#
-
# See IO#readpartial for full details.
-
-
1
def readpartial(maxlen, buf=nil)
-
if maxlen == 0
-
if buf
-
buf.clear
-
return buf
-
else
-
return ""
-
end
-
end
-
if @rbuffer.empty?
-
begin
-
return sysread(maxlen, buf)
-
rescue Errno::EAGAIN
-
retry
-
end
-
end
-
ret = consume_rbuff(maxlen)
-
if buf
-
buf.replace(ret)
-
ret = buf
-
end
-
raise EOFError if ret.empty?
-
ret
-
end
-
-
##
-
# Reads at most +maxlen+ bytes in the non-blocking manner.
-
#
-
# When no data can be read without blocking it raises
-
# OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
-
#
-
# IO::WaitReadable means SSL needs to read internally so read_nonblock
-
# should be called again when the underlying IO is readable.
-
#
-
# IO::WaitWritable means SSL needs to write internally so read_nonblock
-
# should be called again after the underlying IO is writable.
-
#
-
# OpenSSL::Buffering#read_nonblock needs two rescue clause as follows:
-
#
-
# # emulates blocking read (readpartial).
-
# begin
-
# result = ssl.read_nonblock(maxlen)
-
# rescue IO::WaitReadable
-
# IO.select([io])
-
# retry
-
# rescue IO::WaitWritable
-
# IO.select(nil, [io])
-
# retry
-
# end
-
#
-
# Note that one reason that read_nonblock writes to the underlying IO is
-
# when the peer requests a new TLS/SSL handshake. See openssl the FAQ for
-
# more details. http://www.openssl.org/support/faq.html
-
-
1
def read_nonblock(maxlen, buf=nil)
-
if maxlen == 0
-
if buf
-
buf.clear
-
return buf
-
else
-
return ""
-
end
-
end
-
if @rbuffer.empty?
-
return sysread_nonblock(maxlen, buf)
-
end
-
ret = consume_rbuff(maxlen)
-
if buf
-
buf.replace(ret)
-
ret = buf
-
end
-
raise EOFError if ret.empty?
-
ret
-
end
-
-
##
-
# Reads the next "line+ from the stream. Lines are separated by +eol+. If
-
# +limit+ is provided the result will not be longer than the given number of
-
# bytes.
-
#
-
# +eol+ may be a String or Regexp.
-
#
-
# Unlike IO#gets the line read will not be assigned to +$_+.
-
#
-
# Unlike IO#gets the separator must be provided if a limit is provided.
-
-
1
def gets(eol=$/, limit=nil)
-
idx = @rbuffer.index(eol)
-
until @eof
-
break if idx
-
fill_rbuff
-
idx = @rbuffer.index(eol)
-
end
-
if eol.is_a?(Regexp)
-
size = idx ? idx+$&.size : nil
-
else
-
size = idx ? idx+eol.size : nil
-
end
-
if limit and limit >= 0
-
size = [size, limit].min
-
end
-
consume_rbuff(size)
-
end
-
-
##
-
# Executes the block for every line in the stream where lines are separated
-
# by +eol+.
-
#
-
# See also #gets
-
-
1
def each(eol=$/)
-
while line = self.gets(eol)
-
yield line
-
end
-
end
-
1
alias each_line each
-
-
##
-
# Reads lines from the stream which are separated by +eol+.
-
#
-
# See also #gets
-
-
1
def readlines(eol=$/)
-
ary = []
-
while line = self.gets(eol)
-
ary << line
-
end
-
ary
-
end
-
-
##
-
# Reads a line from the stream which is separated by +eol+.
-
#
-
# Raises EOFError if at end of file.
-
-
1
def readline(eol=$/)
-
raise EOFError if eof?
-
gets(eol)
-
end
-
-
##
-
# Reads one character from the stream. Returns nil if called at end of
-
# file.
-
-
1
def getc
-
read(1)
-
end
-
-
##
-
# Calls the given block once for each byte in the stream.
-
-
1
def each_byte # :yields: byte
-
while c = getc
-
yield(c.ord)
-
end
-
end
-
-
##
-
# Reads a one-character string from the stream. Raises an EOFError at end
-
# of file.
-
-
1
def readchar
-
raise EOFError if eof?
-
getc
-
end
-
-
##
-
# Pushes character +c+ back onto the stream such that a subsequent buffered
-
# character read will return it.
-
#
-
# Unlike IO#getc multiple bytes may be pushed back onto the stream.
-
#
-
# Has no effect on unbuffered reads (such as #sysread).
-
-
1
def ungetc(c)
-
@rbuffer[0,0] = c.chr
-
end
-
-
##
-
# Returns true if the stream is at file which means there is no more data to
-
# be read.
-
-
1
def eof?
-
fill_rbuff if !@eof && @rbuffer.empty?
-
@eof && @rbuffer.empty?
-
end
-
1
alias eof eof?
-
-
#
-
# for writing.
-
#
-
1
private
-
-
##
-
# Writes +s+ to the buffer. When the buffer is full or #sync is true the
-
# buffer is flushed to the underlying socket.
-
-
1
def do_write(s)
-
@wbuffer = "" unless defined? @wbuffer
-
@wbuffer << s
-
@sync ||= false
-
if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/)
-
remain = idx ? idx + $/.size : @wbuffer.length
-
nwritten = 0
-
while remain > 0
-
str = @wbuffer[nwritten,remain]
-
begin
-
nwrote = syswrite(str)
-
rescue Errno::EAGAIN
-
retry
-
end
-
remain -= nwrote
-
nwritten += nwrote
-
end
-
@wbuffer[0,nwritten] = ""
-
end
-
end
-
-
1
public
-
-
##
-
# Writes +s+ to the stream. If the argument is not a string it will be
-
# converted using String#to_s. Returns the number of bytes written.
-
-
1
def write(s)
-
do_write(s)
-
s.length
-
end
-
-
##
-
# Writes +str+ in the non-blocking manner.
-
#
-
# If there is buffered data, it is flushed first. This may block.
-
#
-
# write_nonblock returns number of bytes written to the SSL connection.
-
#
-
# When no data can be written without blocking it raises
-
# OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
-
#
-
# IO::WaitReadable means SSL needs to read internally so write_nonblock
-
# should be called again after the underlying IO is readable.
-
#
-
# IO::WaitWritable means SSL needs to write internally so write_nonblock
-
# should be called again after underlying IO is writable.
-
#
-
# So OpenSSL::Buffering#write_nonblock needs two rescue clause as follows.
-
#
-
# # emulates blocking write.
-
# begin
-
# result = ssl.write_nonblock(str)
-
# rescue IO::WaitReadable
-
# IO.select([io])
-
# retry
-
# rescue IO::WaitWritable
-
# IO.select(nil, [io])
-
# retry
-
# end
-
#
-
# Note that one reason that write_nonblock reads from the underlying IO
-
# is when the peer requests a new TLS/SSL handshake. See the openssl FAQ
-
# for more details. http://www.openssl.org/support/faq.html
-
-
1
def write_nonblock(s)
-
flush
-
syswrite_nonblock(s)
-
end
-
-
##
-
# Writes +s+ to the stream. +s+ will be converted to a String using
-
# String#to_s.
-
-
1
def << (s)
-
do_write(s)
-
self
-
end
-
-
##
-
# Writes +args+ to the stream along with a record separator.
-
#
-
# See IO#puts for full details.
-
-
1
def puts(*args)
-
s = ""
-
if args.empty?
-
s << "\n"
-
end
-
args.each{|arg|
-
s << arg.to_s
-
if $/ && /\n\z/ !~ s
-
s << "\n"
-
end
-
}
-
do_write(s)
-
nil
-
end
-
-
##
-
# Writes +args+ to the stream.
-
#
-
# See IO#print for full details.
-
-
1
def print(*args)
-
s = ""
-
args.each{ |arg| s << arg.to_s }
-
do_write(s)
-
nil
-
end
-
-
##
-
# Formats and writes to the stream converting parameters under control of
-
# the format string.
-
#
-
# See Kernel#sprintf for format string details.
-
-
1
def printf(s, *args)
-
do_write(s % args)
-
nil
-
end
-
-
##
-
# Flushes buffered data to the SSLSocket.
-
-
1
def flush
-
osync = @sync
-
@sync = true
-
do_write ""
-
return self
-
ensure
-
@sync = osync
-
end
-
-
##
-
# Closes the SSLSocket and flushes any unwritten data.
-
-
1
def close
-
flush rescue nil
-
sysclose
-
end
-
end
-
#--
-
#
-
# $RCSfile$
-
#
-
# = Ruby-space predefined Cipher subclasses
-
#
-
# = Info
-
# 'OpenSSL for Ruby 2' project
-
# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
-
# All rights reserved.
-
#
-
# = Licence
-
# This program is licenced under the same licence as Ruby.
-
# (See the file 'LICENCE'.)
-
#
-
# = Version
-
# $Id: cipher.rb 33067 2011-08-25 00:52:10Z drbrain $
-
#
-
#++
-
-
1
module OpenSSL
-
1
class Cipher
-
1
%w(AES CAST5 BF DES IDEA RC2 RC4 RC5).each{|name|
-
8
klass = Class.new(Cipher){
-
8
define_method(:initialize){|*args|
-
cipher_name = args.inject(name){|n, arg| "#{n}-#{arg}" }
-
super(cipher_name)
-
}
-
}
-
8
const_set(name, klass)
-
}
-
-
1
%w(128 192 256).each{|keylen|
-
3
klass = Class.new(Cipher){
-
3
define_method(:initialize){|mode|
-
mode ||= "CBC"
-
cipher_name = "AES-#{keylen}-#{mode}"
-
super(cipher_name)
-
}
-
}
-
3
const_set("AES#{keylen}", klass)
-
}
-
-
# Generate, set, and return a random key.
-
# You must call cipher.encrypt or cipher.decrypt before calling this method.
-
1
def random_key
-
str = OpenSSL::Random.random_bytes(self.key_len)
-
self.key = str
-
return str
-
end
-
-
# Generate, set, and return a random iv.
-
# You must call cipher.encrypt or cipher.decrypt before calling this method.
-
1
def random_iv
-
str = OpenSSL::Random.random_bytes(self.iv_len)
-
self.iv = str
-
return str
-
end
-
-
# This class is only provided for backwards compatibility. Use OpenSSL::Cipher in the future.
-
1
class Cipher < Cipher
-
# add warning
-
end
-
end # Cipher
-
end # OpenSSL
-
=begin
-
= Ruby-space definitions that completes C-space funcs for Config
-
-
= Info
-
Copyright (C) 2010 Hiroshi Nakamura <nahi@ruby-lang.org>
-
-
= Licence
-
This program is licenced under the same licence as Ruby.
-
(See the file 'LICENCE'.)
-
-
=end
-
-
1
require 'stringio'
-
-
1
module OpenSSL
-
1
class Config
-
1
include Enumerable
-
-
1
class << self
-
1
def parse(str)
-
c = new()
-
parse_config(StringIO.new(str)).each do |section, hash|
-
c[section] = hash
-
end
-
c
-
end
-
-
1
alias load new
-
-
1
def parse_config(io)
-
begin
-
parse_config_lines(io)
-
rescue ConfigError => e
-
e.message.replace("error in line #{io.lineno}: " + e.message)
-
raise
-
end
-
end
-
-
1
def get_key_string(data, section, key) # :nodoc:
-
if v = data[section] && data[section][key]
-
return v
-
elsif section == 'ENV'
-
if v = ENV[key]
-
return v
-
end
-
end
-
if v = data['default'] && data['default'][key]
-
return v
-
end
-
end
-
-
1
private
-
-
1
def parse_config_lines(io)
-
section = 'default'
-
data = {section => {}}
-
while definition = get_definition(io)
-
definition = clear_comments(definition)
-
next if definition.empty?
-
if definition[0] == ?[
-
if /\[([^\]]*)\]/ =~ definition
-
section = $1.strip
-
data[section] ||= {}
-
else
-
raise ConfigError, "missing close square bracket"
-
end
-
else
-
if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition
-
if $2
-
section = $1
-
key = $2
-
else
-
key = $1
-
end
-
value = unescape_value(data, section, $3)
-
(data[section] ||= {})[key] = value.strip
-
else
-
raise ConfigError, "missing equal sign"
-
end
-
end
-
end
-
data
-
end
-
-
# escape with backslash
-
1
QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/
-
# escape with backslash and doubled dq
-
1
QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/
-
# escaped char map
-
1
ESCAPE_MAP = {
-
"r" => "\r",
-
"n" => "\n",
-
"b" => "\b",
-
"t" => "\t",
-
}
-
-
1
def unescape_value(data, section, value)
-
scanned = []
-
while m = value.match(/['"\\$]/)
-
scanned << m.pre_match
-
c = m[0]
-
value = m.post_match
-
case c
-
when "'"
-
if m = value.match(QUOTE_REGEXP_SQ)
-
scanned << m[1].gsub(/\\(.)/, '\\1')
-
value = m.post_match
-
else
-
break
-
end
-
when '"'
-
if m = value.match(QUOTE_REGEXP_DQ)
-
scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1')
-
value = m.post_match
-
else
-
break
-
end
-
when "\\"
-
c = value.slice!(0, 1)
-
scanned << (ESCAPE_MAP[c] || c)
-
when "$"
-
ref, value = extract_reference(value)
-
refsec = section
-
if ref.index('::')
-
refsec, ref = ref.split('::', 2)
-
end
-
if v = get_key_string(data, refsec, ref)
-
scanned << v
-
else
-
raise ConfigError, "variable has no value"
-
end
-
else
-
raise 'must not reaced'
-
end
-
end
-
scanned << value
-
scanned.join
-
end
-
-
1
def extract_reference(value)
-
rest = ''
-
if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/)
-
value = m[1] || m[2]
-
rest = m.post_match
-
elsif [?(, ?{].include?(value[0])
-
raise ConfigError, "no close brace"
-
end
-
if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/)
-
return m[0], m.post_match + rest
-
else
-
raise
-
end
-
end
-
-
1
def clear_comments(line)
-
# FCOMMENT
-
if m = line.match(/\A([\t\n\f ]*);.*\z/)
-
return m[1]
-
end
-
# COMMENT
-
scanned = []
-
while m = line.match(/[#'"\\]/)
-
scanned << m.pre_match
-
c = m[0]
-
line = m.post_match
-
case c
-
when '#'
-
line = nil
-
break
-
when "'", '"'
-
regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ
-
scanned << c
-
if m = line.match(regexp)
-
scanned << m[0]
-
line = m.post_match
-
else
-
scanned << line
-
line = nil
-
break
-
end
-
when "\\"
-
scanned << c
-
scanned << line.slice!(0, 1)
-
else
-
raise 'must not reaced'
-
end
-
end
-
scanned << line
-
scanned.join
-
end
-
-
1
def get_definition(io)
-
if line = get_line(io)
-
while /[^\\]\\\z/ =~ line
-
if extra = get_line(io)
-
line += extra
-
else
-
break
-
end
-
end
-
return line.strip
-
end
-
end
-
-
1
def get_line(io)
-
if line = io.gets
-
line.gsub(/[\r\n]*/, '')
-
end
-
end
-
end
-
-
1
def initialize(filename = nil)
-
@data = {}
-
if filename
-
File.open(filename.to_s) do |file|
-
Config.parse_config(file).each do |section, hash|
-
self[section] = hash
-
end
-
end
-
end
-
end
-
-
1
def get_value(section, key)
-
if section.nil?
-
raise TypeError.new('nil not allowed')
-
end
-
section = 'default' if section.empty?
-
get_key_string(section, key)
-
end
-
-
1
def value(arg1, arg2 = nil)
-
warn('Config#value is deprecated; use Config#get_value')
-
if arg2.nil?
-
section, key = 'default', arg1
-
else
-
section, key = arg1, arg2
-
end
-
section ||= 'default'
-
section = 'default' if section.empty?
-
get_key_string(section, key)
-
end
-
-
1
def add_value(section, key, value)
-
check_modify
-
(@data[section] ||= {})[key] = value
-
end
-
-
1
def [](section)
-
@data[section] || {}
-
end
-
-
1
def section(name)
-
warn('Config#section is deprecated; use Config#[]')
-
@data[name] || {}
-
end
-
-
1
def []=(section, pairs)
-
check_modify
-
@data[section] ||= {}
-
pairs.each do |key, value|
-
self.add_value(section, key, value)
-
end
-
end
-
-
1
def sections
-
@data.keys
-
end
-
-
1
def to_s
-
ary = []
-
@data.keys.sort.each do |section|
-
ary << "[ #{section} ]\n"
-
@data[section].keys.each do |key|
-
ary << "#{key}=#{@data[section][key]}\n"
-
end
-
ary << "\n"
-
end
-
ary.join
-
end
-
-
1
def each
-
@data.each do |section, hash|
-
hash.each do |key, value|
-
yield [section, key, value]
-
end
-
end
-
end
-
-
1
def inspect
-
"#<#{self.class.name} sections=#{sections.inspect}>"
-
end
-
-
1
protected
-
-
1
def data
-
@data
-
end
-
-
1
private
-
-
1
def initialize_copy(other)
-
@data = other.data.dup
-
end
-
-
1
def check_modify
-
raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen?
-
end
-
-
1
def get_key_string(section, key)
-
Config.get_key_string(@data, section, key)
-
end
-
end
-
end
-
#--
-
#
-
# $RCSfile$
-
#
-
# = Ruby-space predefined Digest subclasses
-
#
-
# = Info
-
# 'OpenSSL for Ruby 2' project
-
# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
-
# All rights reserved.
-
#
-
# = Licence
-
# This program is licenced under the same licence as Ruby.
-
# (See the file 'LICENCE'.)
-
#
-
# = Version
-
# $Id: digest.rb 33067 2011-08-25 00:52:10Z drbrain $
-
#
-
#++
-
-
1
module OpenSSL
-
1
class Digest
-
-
1
alg = %w(DSS DSS1 MD2 MD4 MD5 MDC2 RIPEMD160 SHA SHA1)
-
1
if OPENSSL_VERSION_NUMBER > 0x00908000
-
1
alg += %w(SHA224 SHA256 SHA384 SHA512)
-
end
-
-
# Return the +data+ hash computed with +name+ Digest. +name+ is either the
-
# long name or short name of a supported digest algorithm.
-
#
-
# === Examples
-
#
-
# OpenSSL::Digest.digest("SHA256, "abc")
-
#
-
# which is equivalent to:
-
#
-
# OpenSSL::Digest::SHA256.digest("abc")
-
-
1
def self.digest(name, data)
-
super(data, name)
-
end
-
-
1
alg.each{|name|
-
13
klass = Class.new(Digest){
-
13
define_method(:initialize){|*data|
-
75
if data.length > 1
-
raise ArgumentError,
-
"wrong number of arguments (#{data.length} for 1)"
-
end
-
75
super(name, data.first)
-
}
-
}
-
26
singleton = (class << klass; self; end)
-
13
singleton.class_eval{
-
13
define_method(:digest){|data| Digest.digest(name, data) }
-
13
define_method(:hexdigest){|data| Digest.hexdigest(name, data) }
-
}
-
13
const_set(name, klass)
-
}
-
-
# This class is only provided for backwards compatibility. Use OpenSSL::Digest in the future.
-
1
class Digest < Digest
-
1
def initialize(*args)
-
# add warning
-
super(*args)
-
end
-
end
-
-
end # Digest
-
end # OpenSSL
-
-
=begin
-
= $RCSfile$ -- Ruby-space definitions that completes C-space funcs for SSL
-
-
= Info
-
'OpenSSL for Ruby 2' project
-
Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
-
All rights reserved.
-
-
= Licence
-
This program is licenced under the same licence as Ruby.
-
(See the file 'LICENCE'.)
-
-
= Version
-
$Id: ssl-internal.rb 29189 2010-09-06 01:53:00Z nahi $
-
=end
-
-
1
require "openssl/buffering"
-
1
require "fcntl"
-
-
1
module OpenSSL
-
1
module SSL
-
1
class SSLContext
-
1
DEFAULT_PARAMS = {
-
:ssl_version => "SSLv23",
-
:verify_mode => OpenSSL::SSL::VERIFY_PEER,
-
:ciphers => "ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW",
-
:options => OpenSSL::SSL::OP_ALL,
-
}
-
-
1
DEFAULT_CERT_STORE = OpenSSL::X509::Store.new
-
1
DEFAULT_CERT_STORE.set_default_paths
-
1
if defined?(OpenSSL::X509::V_FLAG_CRL_CHECK_ALL)
-
1
DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
-
end
-
-
1
def set_params(params={})
-
params = DEFAULT_PARAMS.merge(params)
-
params.each{|name, value| self.__send__("#{name}=", value) }
-
if self.verify_mode != OpenSSL::SSL::VERIFY_NONE
-
unless self.ca_file or self.ca_path or self.cert_store
-
self.cert_store = DEFAULT_CERT_STORE
-
end
-
end
-
return params
-
end
-
end
-
-
1
module SocketForwarder
-
1
def addr
-
to_io.addr
-
end
-
-
1
def peeraddr
-
to_io.peeraddr
-
end
-
-
1
def setsockopt(level, optname, optval)
-
to_io.setsockopt(level, optname, optval)
-
end
-
-
1
def getsockopt(level, optname)
-
to_io.getsockopt(level, optname)
-
end
-
-
1
def fcntl(*args)
-
to_io.fcntl(*args)
-
end
-
-
1
def closed?
-
to_io.closed?
-
end
-
-
1
def do_not_reverse_lookup=(flag)
-
to_io.do_not_reverse_lookup = flag
-
end
-
end
-
-
1
module Nonblock
-
1
def initialize(*args)
-
flag = File::NONBLOCK
-
flag |= @io.fcntl(Fcntl::F_GETFL) if defined?(Fcntl::F_GETFL)
-
@io.fcntl(Fcntl::F_SETFL, flag)
-
super
-
end
-
end
-
-
1
def verify_certificate_identity(cert, hostname)
-
should_verify_common_name = true
-
cert.extensions.each{|ext|
-
next if ext.oid != "subjectAltName"
-
ext.value.split(/,\s+/).each{|general_name|
-
if /\ADNS:(.*)/ =~ general_name
-
should_verify_common_name = false
-
reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
-
return true if /\A#{reg}\z/i =~ hostname
-
elsif /\AIP Address:(.*)/ =~ general_name
-
should_verify_common_name = false
-
return true if $1 == hostname
-
end
-
}
-
}
-
if should_verify_common_name
-
cert.subject.to_a.each{|oid, value|
-
if oid == "CN"
-
reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
-
return true if /\A#{reg}\z/i =~ hostname
-
end
-
}
-
end
-
return false
-
end
-
1
module_function :verify_certificate_identity
-
-
1
class SSLSocket
-
1
include Buffering
-
1
include SocketForwarder
-
1
include Nonblock
-
-
1
def post_connection_check(hostname)
-
unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname)
-
raise SSLError, "hostname does not match the server certificate"
-
end
-
return true
-
end
-
-
1
def session
-
SSL::Session.new(self)
-
rescue SSL::Session::SessionError
-
nil
-
end
-
end
-
-
1
class SSLServer
-
1
include SocketForwarder
-
1
attr_accessor :start_immediately
-
-
1
def initialize(svr, ctx)
-
@svr = svr
-
@ctx = ctx
-
unless ctx.session_id_context
-
session_id = OpenSSL::Digest::MD5.hexdigest($0)
-
@ctx.session_id_context = session_id
-
end
-
@start_immediately = true
-
end
-
-
1
def to_io
-
@svr
-
end
-
-
1
def listen(backlog=5)
-
@svr.listen(backlog)
-
end
-
-
1
def shutdown(how=Socket::SHUT_RDWR)
-
@svr.shutdown(how)
-
end
-
-
1
def accept
-
sock = @svr.accept
-
begin
-
ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
-
ssl.sync_close = true
-
ssl.accept if @start_immediately
-
ssl
-
rescue SSLError => ex
-
sock.close
-
raise ex
-
end
-
end
-
-
1
def close
-
@svr.close
-
end
-
end
-
end
-
end
-
=begin
-
= $RCSfile$ -- Ruby-space definitions that completes C-space funcs for X509 and subclasses
-
-
= Info
-
'OpenSSL for Ruby 2' project
-
Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
-
All rights reserved.
-
-
= Licence
-
This program is licenced under the same licence as Ruby.
-
(See the file 'LICENCE'.)
-
-
= Version
-
$Id: x509-internal.rb 32663 2011-07-25 04:51:26Z nahi $
-
=end
-
-
1
module OpenSSL
-
1
module X509
-
1
class ExtensionFactory
-
1
def create_extension(*arg)
-
if arg.size > 1
-
create_ext(*arg)
-
else
-
send("create_ext_from_"+arg[0].class.name.downcase, arg[0])
-
end
-
end
-
-
1
def create_ext_from_array(ary)
-
raise ExtensionError, "unexpected array form" if ary.size > 3
-
create_ext(ary[0], ary[1], ary[2])
-
end
-
-
1
def create_ext_from_string(str) # "oid = critical, value"
-
oid, value = str.split(/=/, 2)
-
oid.strip!
-
value.strip!
-
create_ext(oid, value)
-
end
-
-
1
def create_ext_from_hash(hash)
-
create_ext(hash["oid"], hash["value"], hash["critical"])
-
end
-
end
-
-
1
class Extension
-
1
def to_s # "oid = critical, value"
-
str = self.oid
-
str << " = "
-
str << "critical, " if self.critical?
-
str << self.value.gsub(/\n/, ", ")
-
end
-
-
1
def to_h # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false}
-
{"oid"=>self.oid,"value"=>self.value,"critical"=>self.critical?}
-
end
-
-
1
def to_a
-
[ self.oid, self.value, self.critical? ]
-
end
-
end
-
-
1
class Name
-
1
module RFC2253DN
-
1
Special = ',=+<>#;'
-
1
HexChar = /[0-9a-fA-F]/
-
1
HexPair = /#{HexChar}#{HexChar}/
-
1
HexString = /#{HexPair}+/
-
1
Pair = /\\(?:[#{Special}]|\\|"|#{HexPair})/
-
1
StringChar = /[^#{Special}\\"]/
-
1
QuoteChar = /[^\\"]/
-
1
AttributeType = /[a-zA-Z][0-9a-zA-Z]*|[0-9]+(?:\.[0-9]+)*/
-
1
AttributeValue = /
-
(?!["#])((?:#{StringChar}|#{Pair})*)|
-
\#(#{HexString})|
-
"((?:#{QuoteChar}|#{Pair})*)"
-
/x
-
1
TypeAndValue = /\A(#{AttributeType})=#{AttributeValue}/
-
-
1
module_function
-
-
1
def expand_pair(str)
-
return nil unless str
-
return str.gsub(Pair){
-
pair = $&
-
case pair.size
-
when 2 then pair[1,1]
-
when 3 then Integer("0x#{pair[1,2]}").chr
-
else raise OpenSSL::X509::NameError, "invalid pair: #{str}"
-
end
-
}
-
end
-
-
1
def expand_hexstring(str)
-
return nil unless str
-
der = str.gsub(HexPair){$&.to_i(16).chr }
-
a1 = OpenSSL::ASN1.decode(der)
-
return a1.value, a1.tag
-
end
-
-
1
def expand_value(str1, str2, str3)
-
value = expand_pair(str1)
-
value, tag = expand_hexstring(str2) unless value
-
value = expand_pair(str3) unless value
-
return value, tag
-
end
-
-
1
def scan(dn)
-
str = dn
-
ary = []
-
while true
-
if md = TypeAndValue.match(str)
-
remain = md.post_match
-
type = md[1]
-
value, tag = expand_value(md[2], md[3], md[4]) rescue nil
-
if value
-
type_and_value = [type, value]
-
type_and_value.push(tag) if tag
-
ary.unshift(type_and_value)
-
if remain.length > 2 && remain[0] == ?,
-
str = remain[1..-1]
-
next
-
elsif remain.length > 2 && remain[0] == ?+
-
raise OpenSSL::X509::NameError,
-
"multi-valued RDN is not supported: #{dn}"
-
elsif remain.empty?
-
break
-
end
-
end
-
end
-
msg_dn = dn[0, dn.length - str.length] + " =>" + str
-
raise OpenSSL::X509::NameError, "malformed RDN: #{msg_dn}"
-
end
-
return ary
-
end
-
end
-
-
1
class << self
-
1
def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE)
-
ary = OpenSSL::X509::Name::RFC2253DN.scan(str)
-
self.new(ary, template)
-
end
-
-
1
def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE)
-
ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=", 2) }
-
self.new(ary, template)
-
end
-
-
1
alias parse parse_openssl
-
end
-
end
-
-
1
class StoreContext
-
1
def cleanup
-
warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE
-
end
-
end
-
end
-
end
-
# == Pretty-printer for Ruby objects.
-
#
-
# = Which seems better?
-
#
-
# non-pretty-printed output by #p is:
-
# #<PP:0x81fedf0 @genspace=#<Proc:0x81feda0>, @group_queue=#<PrettyPrint::GroupQueue:0x81fed3c @queue=[[#<PrettyPrint::Group:0x81fed78 @breakables=[], @depth=0, @break=false>], []]>, @buffer=[], @newline="\n", @group_stack=[#<PrettyPrint::Group:0x81fed78 @breakables=[], @depth=0, @break=false>], @buffer_width=0, @indent=0, @maxwidth=79, @output_width=2, @output=#<IO:0x8114ee4>>
-
#
-
# pretty-printed output by #pp is:
-
# #<PP:0x81fedf0
-
# @buffer=[],
-
# @buffer_width=0,
-
# @genspace=#<Proc:0x81feda0>,
-
# @group_queue=
-
# #<PrettyPrint::GroupQueue:0x81fed3c
-
# @queue=
-
# [[#<PrettyPrint::Group:0x81fed78 @break=false, @breakables=[], @depth=0>],
-
# []]>,
-
# @group_stack=
-
# [#<PrettyPrint::Group:0x81fed78 @break=false, @breakables=[], @depth=0>],
-
# @indent=0,
-
# @maxwidth=79,
-
# @newline="\n",
-
# @output=#<IO:0x8114ee4>,
-
# @output_width=2>
-
#
-
# I like the latter. If you do too, this library is for you.
-
#
-
# = Usage
-
#
-
# pp(obj)
-
#
-
# output +obj+ to +$>+ in pretty printed format.
-
#
-
# It returns +nil+.
-
#
-
# = Output Customization
-
# To define your customized pretty printing function for your classes,
-
# redefine a method #pretty_print(+pp+) in the class.
-
# It takes an argument +pp+ which is an instance of the class PP.
-
# The method should use PP#text, PP#breakable, PP#nest, PP#group and
-
# PP#pp to print the object.
-
#
-
# = Author
-
# Tanaka Akira <akr@m17n.org>
-
-
1
require 'prettyprint'
-
-
1
module Kernel
-
# returns a pretty printed object as a string.
-
1
def pretty_inspect
-
PP.pp(self, '')
-
end
-
-
1
private
-
# prints arguments in pretty form.
-
#
-
# pp returns argument(s).
-
1
def pp(*objs) # :doc:
-
objs.each {|obj|
-
PP.pp(obj)
-
}
-
objs.size <= 1 ? objs.first : objs
-
end
-
1
module_function :pp
-
end
-
-
1
class PP < PrettyPrint
-
# Outputs +obj+ to +out+ in pretty printed format of
-
# +width+ columns in width.
-
#
-
# If +out+ is omitted, +$>+ is assumed.
-
# If +width+ is omitted, 79 is assumed.
-
#
-
# PP.pp returns +out+.
-
1
def PP.pp(obj, out=$>, width=79)
-
q = PP.new(out, width)
-
q.guard_inspect_key {q.pp obj}
-
q.flush
-
#$pp = q
-
out << "\n"
-
end
-
-
# Outputs +obj+ to +out+ like PP.pp but with no indent and
-
# newline.
-
#
-
# PP.singleline_pp returns +out+.
-
1
def PP.singleline_pp(obj, out=$>)
-
q = SingleLine.new(out)
-
q.guard_inspect_key {q.pp obj}
-
q.flush
-
out
-
end
-
-
# :stopdoc:
-
1
def PP.mcall(obj, mod, meth, *args, &block)
-
mod.instance_method(meth).bind(obj).call(*args, &block)
-
end
-
# :startdoc:
-
-
1
@sharing_detection = false
-
1
class << self
-
# Returns the sharing detection flag as a boolean value.
-
# It is false by default.
-
1
attr_accessor :sharing_detection
-
end
-
-
1
module PPMethods
-
1
def guard_inspect_key
-
if Thread.current[:__recursive_key__] == nil
-
Thread.current[:__recursive_key__] = {}.untrust
-
end
-
-
if Thread.current[:__recursive_key__][:inspect] == nil
-
Thread.current[:__recursive_key__][:inspect] = {}.untrust
-
end
-
-
save = Thread.current[:__recursive_key__][:inspect]
-
-
begin
-
Thread.current[:__recursive_key__][:inspect] = {}.untrust
-
yield
-
ensure
-
Thread.current[:__recursive_key__][:inspect] = save
-
end
-
end
-
-
1
def check_inspect_key(id)
-
Thread.current[:__recursive_key__] &&
-
Thread.current[:__recursive_key__][:inspect] &&
-
Thread.current[:__recursive_key__][:inspect].include?(id)
-
end
-
1
def push_inspect_key(id)
-
Thread.current[:__recursive_key__][:inspect][id] = true
-
end
-
1
def pop_inspect_key(id)
-
Thread.current[:__recursive_key__][:inspect].delete id
-
end
-
-
# Adds +obj+ to the pretty printing buffer
-
# using Object#pretty_print or Object#pretty_print_cycle.
-
#
-
# Object#pretty_print_cycle is used when +obj+ is already
-
# printed, a.k.a the object reference chain has a cycle.
-
1
def pp(obj)
-
id = obj.object_id
-
-
if check_inspect_key(id)
-
group {obj.pretty_print_cycle self}
-
return
-
end
-
-
begin
-
push_inspect_key(id)
-
group {obj.pretty_print self}
-
ensure
-
pop_inspect_key(id) unless PP.sharing_detection
-
end
-
end
-
-
# A convenience method which is same as follows:
-
#
-
# group(1, '#<' + obj.class.name, '>') { ... }
-
1
def object_group(obj, &block) # :yield:
-
group(1, '#<' + obj.class.name, '>', &block)
-
end
-
-
1
PointerMask = (1 << ([""].pack("p").size * 8)) - 1
-
-
1
case Object.new.inspect
-
when /\A\#<Object:0x([0-9a-f]+)>\z/
-
1
PointerFormat = "%0#{$1.length}x"
-
else
-
PointerFormat = "%x"
-
end
-
-
1
def object_address_group(obj, &block)
-
id = PointerFormat % (obj.object_id * 2 & PointerMask)
-
group(1, "\#<#{obj.class}:0x#{id}", '>', &block)
-
end
-
-
# A convenience method which is same as follows:
-
#
-
# text ','
-
# breakable
-
1
def comma_breakable
-
text ','
-
breakable
-
end
-
-
# Adds a separated list.
-
# The list is separated by comma with breakable space, by default.
-
#
-
# #seplist iterates the +list+ using +iter_method+.
-
# It yields each object to the block given for #seplist.
-
# The procedure +separator_proc+ is called between each yields.
-
#
-
# If the iteration is zero times, +separator_proc+ is not called at all.
-
#
-
# If +separator_proc+ is nil or not given,
-
# +lambda { comma_breakable }+ is used.
-
# If +iter_method+ is not given, :each is used.
-
#
-
# For example, following 3 code fragments has similar effect.
-
#
-
# q.seplist([1,2,3]) {|v| xxx v }
-
#
-
# q.seplist([1,2,3], lambda { q.comma_breakable }, :each) {|v| xxx v }
-
#
-
# xxx 1
-
# q.comma_breakable
-
# xxx 2
-
# q.comma_breakable
-
# xxx 3
-
1
def seplist(list, sep=nil, iter_method=:each) # :yield: element
-
sep ||= lambda { comma_breakable }
-
first = true
-
list.__send__(iter_method) {|*v|
-
if first
-
first = false
-
else
-
sep.call
-
end
-
yield(*v)
-
}
-
end
-
-
1
def pp_object(obj)
-
object_address_group(obj) {
-
seplist(obj.pretty_print_instance_variables, lambda { text ',' }) {|v|
-
breakable
-
v = v.to_s if Symbol === v
-
text v
-
text '='
-
group(1) {
-
breakable ''
-
pp(obj.instance_eval(v))
-
}
-
}
-
}
-
end
-
-
1
def pp_hash(obj)
-
group(1, '{', '}') {
-
seplist(obj, nil, :each_pair) {|k, v|
-
group {
-
pp k
-
text '=>'
-
group(1) {
-
breakable ''
-
pp v
-
}
-
}
-
}
-
}
-
end
-
end
-
-
1
include PPMethods
-
-
1
class SingleLine < PrettyPrint::SingleLine
-
1
include PPMethods
-
end
-
-
1
module ObjectMixin
-
# 1. specific pretty_print
-
# 2. specific inspect
-
# 3. specific to_s
-
# 4. generic pretty_print
-
-
# A default pretty printing method for general objects.
-
# It calls #pretty_print_instance_variables to list instance variables.
-
#
-
# If +self+ has a customized (redefined) #inspect method,
-
# the result of self.inspect is used but it obviously has no
-
# line break hints.
-
#
-
# This module provides predefined #pretty_print methods for some of
-
# the most commonly used built-in classes for convenience.
-
1
def pretty_print(q)
-
method_method = Object.instance_method(:method).bind(self)
-
begin
-
inspect_method = method_method.call(:inspect)
-
rescue NameError
-
end
-
begin
-
to_s_method = method_method.call(:to_s)
-
rescue NameError
-
end
-
if inspect_method && /\(Kernel\)#/ !~ inspect_method.inspect
-
q.text self.inspect
-
elsif !inspect_method && self.respond_to?(:inspect)
-
q.text self.inspect
-
elsif to_s_method && /\(Kernel\)#/ !~ to_s_method.inspect
-
q.text self.to_s
-
elsif !to_s_method && self.respond_to?(:to_s)
-
q.text self.to_s
-
else
-
q.pp_object(self)
-
end
-
end
-
-
# A default pretty printing method for general objects that are
-
# detected as part of a cycle.
-
1
def pretty_print_cycle(q)
-
q.object_address_group(self) {
-
q.breakable
-
q.text '...'
-
}
-
end
-
-
# Returns a sorted array of instance variable names.
-
#
-
# This method should return an array of names of instance variables as symbols or strings as:
-
# +[:@a, :@b]+.
-
1
def pretty_print_instance_variables
-
instance_variables.sort
-
end
-
-
# Is #inspect implementation using #pretty_print.
-
# If you implement #pretty_print, it can be used as follows.
-
#
-
# alias inspect pretty_print_inspect
-
#
-
# However, doing this requires that every class that #inspect is called on
-
# implement #pretty_print, or a RuntimeError will be raised.
-
1
def pretty_print_inspect
-
if /\(PP::ObjectMixin\)#/ =~ Object.instance_method(:method).bind(self).call(:pretty_print).inspect
-
raise "pretty_print is not overridden for #{self.class}"
-
end
-
PP.singleline_pp(self, '')
-
end
-
end
-
end
-
-
1
class Array
-
1
def pretty_print(q)
-
q.group(1, '[', ']') {
-
q.seplist(self) {|v|
-
q.pp v
-
}
-
}
-
end
-
-
1
def pretty_print_cycle(q)
-
q.text(empty? ? '[]' : '[...]')
-
end
-
end
-
-
1
class Hash
-
1
def pretty_print(q)
-
q.pp_hash self
-
end
-
-
1
def pretty_print_cycle(q)
-
q.text(empty? ? '{}' : '{...}')
-
end
-
end
-
-
1
class << ENV
-
1
def pretty_print(q)
-
h = {}
-
ENV.keys.sort.each {|k|
-
h[k] = ENV[k]
-
}
-
q.pp_hash h
-
end
-
end
-
-
1
class Struct
-
1
def pretty_print(q)
-
q.group(1, sprintf("#<struct %s", PP.mcall(self, Kernel, :class).name), '>') {
-
q.seplist(PP.mcall(self, Struct, :members), lambda { q.text "," }) {|member|
-
q.breakable
-
q.text member.to_s
-
q.text '='
-
q.group(1) {
-
q.breakable ''
-
q.pp self[member]
-
}
-
}
-
}
-
end
-
-
1
def pretty_print_cycle(q)
-
q.text sprintf("#<struct %s:...>", PP.mcall(self, Kernel, :class).name)
-
end
-
end
-
-
1
class Range
-
1
def pretty_print(q)
-
q.pp self.begin
-
q.breakable ''
-
q.text(self.exclude_end? ? '...' : '..')
-
q.breakable ''
-
q.pp self.end
-
end
-
end
-
-
1
class File < IO
-
1
class Stat
-
1
def pretty_print(q)
-
require 'etc.so'
-
q.object_group(self) {
-
q.breakable
-
q.text sprintf("dev=0x%x", self.dev); q.comma_breakable
-
q.text "ino="; q.pp self.ino; q.comma_breakable
-
q.group {
-
m = self.mode
-
q.text sprintf("mode=0%o", m)
-
q.breakable
-
q.text sprintf("(%s %c%c%c%c%c%c%c%c%c)",
-
self.ftype,
-
(m & 0400 == 0 ? ?- : ?r),
-
(m & 0200 == 0 ? ?- : ?w),
-
(m & 0100 == 0 ? (m & 04000 == 0 ? ?- : ?S) :
-
(m & 04000 == 0 ? ?x : ?s)),
-
(m & 0040 == 0 ? ?- : ?r),
-
(m & 0020 == 0 ? ?- : ?w),
-
(m & 0010 == 0 ? (m & 02000 == 0 ? ?- : ?S) :
-
(m & 02000 == 0 ? ?x : ?s)),
-
(m & 0004 == 0 ? ?- : ?r),
-
(m & 0002 == 0 ? ?- : ?w),
-
(m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) :
-
(m & 01000 == 0 ? ?x : ?t)))
-
}
-
q.comma_breakable
-
q.text "nlink="; q.pp self.nlink; q.comma_breakable
-
q.group {
-
q.text "uid="; q.pp self.uid
-
begin
-
pw = Etc.getpwuid(self.uid)
-
rescue ArgumentError
-
end
-
if pw
-
q.breakable; q.text "(#{pw.name})"
-
end
-
}
-
q.comma_breakable
-
q.group {
-
q.text "gid="; q.pp self.gid
-
begin
-
gr = Etc.getgrgid(self.gid)
-
rescue ArgumentError
-
end
-
if gr
-
q.breakable; q.text "(#{gr.name})"
-
end
-
}
-
q.comma_breakable
-
q.group {
-
q.text sprintf("rdev=0x%x", self.rdev)
-
q.breakable
-
q.text sprintf('(%d, %d)', self.rdev_major, self.rdev_minor)
-
}
-
q.comma_breakable
-
q.text "size="; q.pp self.size; q.comma_breakable
-
q.text "blksize="; q.pp self.blksize; q.comma_breakable
-
q.text "blocks="; q.pp self.blocks; q.comma_breakable
-
q.group {
-
t = self.atime
-
q.text "atime="; q.pp t
-
q.breakable; q.text "(#{t.tv_sec})"
-
}
-
q.comma_breakable
-
q.group {
-
t = self.mtime
-
q.text "mtime="; q.pp t
-
q.breakable; q.text "(#{t.tv_sec})"
-
}
-
q.comma_breakable
-
q.group {
-
t = self.ctime
-
q.text "ctime="; q.pp t
-
q.breakable; q.text "(#{t.tv_sec})"
-
}
-
}
-
end
-
end
-
end
-
-
1
class MatchData
-
1
def pretty_print(q)
-
nc = []
-
self.regexp.named_captures.each {|name, indexes|
-
indexes.each {|i| nc[i] = name }
-
}
-
q.object_group(self) {
-
q.breakable
-
q.seplist(0...self.size, lambda { q.breakable }) {|i|
-
if i == 0
-
q.pp self[i]
-
else
-
if nc[i]
-
q.text nc[i]
-
else
-
q.pp i
-
end
-
q.text ':'
-
q.pp self[i]
-
end
-
}
-
}
-
end
-
end
-
-
1
class Object < BasicObject
-
1
include PP::ObjectMixin
-
end
-
-
1
[Numeric, Symbol, FalseClass, TrueClass, NilClass, Module].each {|c|
-
6
c.class_eval {
-
6
def pretty_print_cycle(q)
-
q.text inspect
-
end
-
}
-
}
-
-
1
[Numeric, FalseClass, TrueClass, Module].each {|c|
-
4
c.class_eval {
-
4
def pretty_print(q)
-
q.text inspect
-
end
-
}
-
}
-
# This class implements a pretty printing algorithm. It finds line breaks and
-
# nice indentations for grouped structure.
-
#
-
# By default, the class assumes that primitive elements are strings and each
-
# byte in the strings have single column in width. But it can be used for
-
# other situations by giving suitable arguments for some methods:
-
# * newline object and space generation block for PrettyPrint.new
-
# * optional width argument for PrettyPrint#text
-
# * PrettyPrint#breakable
-
#
-
# There are several candidate uses:
-
# * text formatting using proportional fonts
-
# * multibyte characters which has columns different to number of bytes
-
# * non-string formatting
-
#
-
# == Bugs
-
# * Box based formatting?
-
# * Other (better) model/algorithm?
-
#
-
# == References
-
# Christian Lindig, Strictly Pretty, March 2000,
-
# http://www.st.cs.uni-sb.de/~lindig/papers/#pretty
-
#
-
# Philip Wadler, A prettier printer, March 1998,
-
# http://homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier
-
#
-
# == Author
-
# Tanaka Akira <akr@m17n.org>
-
#
-
1
class PrettyPrint
-
-
# This is a convenience method which is same as follows:
-
#
-
# begin
-
# q = PrettyPrint.new(output, maxwidth, newline, &genspace)
-
# ...
-
# q.flush
-
# output
-
# end
-
#
-
1
def PrettyPrint.format(output='', maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n})
-
q = PrettyPrint.new(output, maxwidth, newline, &genspace)
-
yield q
-
q.flush
-
output
-
end
-
-
# This is similar to PrettyPrint::format but the result has no breaks.
-
#
-
# +maxwidth+, +newline+ and +genspace+ are ignored.
-
#
-
# The invocation of +breakable+ in the block doesn't break a line and is
-
# treated as just an invocation of +text+.
-
#
-
1
def PrettyPrint.singleline_format(output='', maxwidth=nil, newline=nil, genspace=nil)
-
q = SingleLine.new(output)
-
yield q
-
output
-
end
-
-
# Creates a buffer for pretty printing.
-
#
-
# +output+ is an output target. If it is not specified, '' is assumed. It
-
# should have a << method which accepts the first argument +obj+ of
-
# PrettyPrint#text, the first argument +sep+ of PrettyPrint#breakable, the
-
# first argument +newline+ of PrettyPrint.new, and the result of a given
-
# block for PrettyPrint.new.
-
#
-
# +maxwidth+ specifies maximum line length. If it is not specified, 79 is
-
# assumed. However actual outputs may overflow +maxwidth+ if long
-
# non-breakable texts are provided.
-
#
-
# +newline+ is used for line breaks. "\n" is used if it is not specified.
-
#
-
# The block is used to generate spaces. {|width| ' ' * width} is used if it
-
# is not given.
-
#
-
1
def initialize(output='', maxwidth=79, newline="\n", &genspace)
-
@output = output
-
@maxwidth = maxwidth
-
@newline = newline
-
@genspace = genspace || lambda {|n| ' ' * n}
-
-
@output_width = 0
-
@buffer_width = 0
-
@buffer = []
-
-
root_group = Group.new(0)
-
@group_stack = [root_group]
-
@group_queue = GroupQueue.new(root_group)
-
@indent = 0
-
end
-
1
attr_reader :output, :maxwidth, :newline, :genspace
-
1
attr_reader :indent, :group_queue
-
-
# Returns the group most recently added to the stack.
-
1
def current_group
-
@group_stack.last
-
end
-
-
# first? is a predicate to test the call is a first call to first? with
-
# current group.
-
#
-
# It is useful to format comma separated values as:
-
#
-
# q.group(1, '[', ']') {
-
# xxx.each {|yyy|
-
# unless q.first?
-
# q.text ','
-
# q.breakable
-
# end
-
# ... pretty printing yyy ...
-
# }
-
# }
-
#
-
# first? is obsoleted in 1.8.2.
-
#
-
1
def first?
-
warn "PrettyPrint#first? is obsoleted at 1.8.2."
-
current_group.first?
-
end
-
-
# Breaks the buffer into lines that are shorter than #maxwidth
-
1
def break_outmost_groups
-
while @maxwidth < @output_width + @buffer_width
-
return unless group = @group_queue.deq
-
until group.breakables.empty?
-
data = @buffer.shift
-
@output_width = data.output(@output, @output_width)
-
@buffer_width -= data.width
-
end
-
while !@buffer.empty? && Text === @buffer.first
-
text = @buffer.shift
-
@output_width = text.output(@output, @output_width)
-
@buffer_width -= text.width
-
end
-
end
-
end
-
-
# This adds +obj+ as a text of +width+ columns in width.
-
#
-
# If +width+ is not specified, obj.length is used.
-
#
-
1
def text(obj, width=obj.length)
-
if @buffer.empty?
-
@output << obj
-
@output_width += width
-
else
-
text = @buffer.last
-
unless Text === text
-
text = Text.new
-
@buffer << text
-
end
-
text.add(obj, width)
-
@buffer_width += width
-
break_outmost_groups
-
end
-
end
-
-
# This is similar to #breakable except
-
# the decision to break or not is determined individually.
-
#
-
# Two #fill_breakable under a group may cause 4 results:
-
# (break,break), (break,non-break), (non-break,break), (non-break,non-break).
-
# This is different to #breakable because two #breakable under a group
-
# may cause 2 results:
-
# (break,break), (non-break,non-break).
-
#
-
# The text sep+ is inserted if a line is not broken at this point.
-
#
-
# If +sep+ is not specified, " " is used.
-
#
-
# If +width+ is not specified, +sep.length+ is used. You will have to
-
# specify this when +sep+ is a multibyte character, for example.
-
#
-
1
def fill_breakable(sep=' ', width=sep.length)
-
group { breakable sep, width }
-
end
-
-
# This says "you can break a line here if necessary", and a +width+\-column
-
# text +sep+ is inserted if a line is not broken at the point.
-
#
-
# If +sep+ is not specified, " " is used.
-
#
-
# If +width+ is not specified, +sep.length+ is used. You will have to
-
# specify this when +sep+ is a multibyte character, for example.
-
#
-
1
def breakable(sep=' ', width=sep.length)
-
group = @group_stack.last
-
if group.break?
-
flush
-
@output << @newline
-
@output << @genspace.call(@indent)
-
@output_width = @indent
-
@buffer_width = 0
-
else
-
@buffer << Breakable.new(sep, width, self)
-
@buffer_width += width
-
break_outmost_groups
-
end
-
end
-
-
# Groups line break hints added in the block. The line break hints are all
-
# to be used or not.
-
#
-
# If +indent+ is specified, the method call is regarded as nested by
-
# nest(indent) { ... }.
-
#
-
# If +open_obj+ is specified, <tt>text open_obj, open_width</tt> is called
-
# before grouping. If +close_obj+ is specified, <tt>text close_obj,
-
# close_width</tt> is called after grouping.
-
#
-
1
def group(indent=0, open_obj='', close_obj='', open_width=open_obj.length, close_width=close_obj.length)
-
text open_obj, open_width
-
group_sub {
-
nest(indent) {
-
yield
-
}
-
}
-
text close_obj, close_width
-
end
-
-
1
def group_sub
-
group = Group.new(@group_stack.last.depth + 1)
-
@group_stack.push group
-
@group_queue.enq group
-
begin
-
yield
-
ensure
-
@group_stack.pop
-
if group.breakables.empty?
-
@group_queue.delete group
-
end
-
end
-
end
-
-
# Increases left margin after newline with +indent+ for line breaks added in
-
# the block.
-
#
-
1
def nest(indent)
-
@indent += indent
-
begin
-
yield
-
ensure
-
@indent -= indent
-
end
-
end
-
-
# outputs buffered data.
-
#
-
1
def flush
-
@buffer.each {|data|
-
@output_width = data.output(@output, @output_width)
-
}
-
@buffer.clear
-
@buffer_width = 0
-
end
-
-
1
class Text
-
1
def initialize
-
@objs = []
-
@width = 0
-
end
-
1
attr_reader :width
-
-
1
def output(out, output_width)
-
@objs.each {|obj| out << obj}
-
output_width + @width
-
end
-
-
1
def add(obj, width)
-
@objs << obj
-
@width += width
-
end
-
end
-
-
1
class Breakable
-
1
def initialize(sep, width, q)
-
@obj = sep
-
@width = width
-
@pp = q
-
@indent = q.indent
-
@group = q.current_group
-
@group.breakables.push self
-
end
-
1
attr_reader :obj, :width, :indent
-
-
1
def output(out, output_width)
-
@group.breakables.shift
-
if @group.break?
-
out << @pp.newline
-
out << @pp.genspace.call(@indent)
-
@indent
-
else
-
@pp.group_queue.delete @group if @group.breakables.empty?
-
out << @obj
-
output_width + @width
-
end
-
end
-
end
-
-
1
class Group
-
1
def initialize(depth)
-
@depth = depth
-
@breakables = []
-
@break = false
-
end
-
1
attr_reader :depth, :breakables
-
-
1
def break
-
@break = true
-
end
-
-
1
def break?
-
@break
-
end
-
-
1
def first?
-
if defined? @first
-
false
-
else
-
@first = false
-
true
-
end
-
end
-
end
-
-
1
class GroupQueue
-
1
def initialize(*groups)
-
@queue = []
-
groups.each {|g| enq g}
-
end
-
-
1
def enq(group)
-
depth = group.depth
-
@queue << [] until depth < @queue.length
-
@queue[depth] << group
-
end
-
-
1
def deq
-
@queue.each {|gs|
-
(gs.length-1).downto(0) {|i|
-
unless gs[i].breakables.empty?
-
group = gs.slice!(i, 1).first
-
group.break
-
return group
-
end
-
}
-
gs.each {|group| group.break}
-
gs.clear
-
}
-
return nil
-
end
-
-
1
def delete(group)
-
@queue[group.depth].delete(group)
-
end
-
end
-
-
1
class SingleLine
-
1
def initialize(output, maxwidth=nil, newline=nil)
-
@output = output
-
@first = [true]
-
end
-
-
1
def text(obj, width=nil)
-
@output << obj
-
end
-
-
1
def breakable(sep=' ', width=nil)
-
@output << sep
-
end
-
-
1
def nest(indent)
-
yield
-
end
-
-
1
def group(indent=nil, open_obj='', close_obj='', open_width=nil, close_width=nil)
-
@first.push true
-
@output << open_obj
-
yield
-
@output << close_obj
-
@first.pop
-
end
-
-
1
def flush
-
end
-
-
1
def first?
-
result = @first[-1]
-
@first[-1] = false
-
result
-
end
-
end
-
end
-
#
-
# $originalId: parser.rb,v 1.8 2006/07/06 11:42:07 aamine Exp $
-
#
-
# Copyright (c) 1999-2006 Minero Aoki
-
#
-
# This program is free software.
-
# You can distribute/modify this program under the same terms of ruby.
-
#
-
# As a special exception, when this code is copied by Racc
-
# into a Racc output file, you may use that output file
-
# without restriction.
-
#
-
-
1
unless defined?(NotImplementedError)
-
NotImplementedError = NotImplementError
-
end
-
-
1
module Racc
-
1
class ParseError < StandardError; end
-
end
-
1
unless defined?(::ParseError)
-
1
ParseError = Racc::ParseError
-
end
-
-
1
module Racc
-
-
1
unless defined?(Racc_No_Extentions)
-
1
Racc_No_Extentions = false
-
end
-
-
1
class Parser
-
-
1
Racc_Runtime_Version = '1.4.6'
-
1
Racc_Runtime_Revision = %w$originalRevision: 1.8 $[1]
-
-
1
Racc_Runtime_Core_Version_R = '1.4.6'
-
1
Racc_Runtime_Core_Revision_R = %w$originalRevision: 1.8 $[1]
-
1
begin
-
1
require 'racc/cparse'
-
# Racc_Runtime_Core_Version_C = (defined in extention)
-
1
Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split[2]
-
1
unless new.respond_to?(:_racc_do_parse_c, true)
-
raise LoadError, 'old cparse.so'
-
end
-
1
if Racc_No_Extentions
-
raise LoadError, 'selecting ruby version of racc runtime core'
-
end
-
-
1
Racc_Main_Parsing_Routine = :_racc_do_parse_c
-
1
Racc_YY_Parse_Method = :_racc_yyparse_c
-
1
Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C
-
1
Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_C
-
1
Racc_Runtime_Type = 'c'
-
rescue LoadError
-
Racc_Main_Parsing_Routine = :_racc_do_parse_rb
-
Racc_YY_Parse_Method = :_racc_yyparse_rb
-
Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R
-
Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R
-
Racc_Runtime_Type = 'ruby'
-
end
-
-
1
def Parser.racc_runtime_type
-
Racc_Runtime_Type
-
end
-
-
1
private
-
-
1
def _racc_setup
-
9548
@yydebug = false unless self.class::Racc_debug_parser
-
9548
@yydebug = false unless defined?(@yydebug)
-
9548
if @yydebug
-
@racc_debug_out = $stderr unless defined?(@racc_debug_out)
-
@racc_debug_out ||= $stderr
-
end
-
9548
arg = self.class::Racc_arg
-
9548
arg[13] = true if arg.size < 14
-
9548
arg
-
end
-
-
1
def _racc_init_sysvars
-
@racc_state = [0]
-
@racc_tstack = []
-
@racc_vstack = []
-
-
@racc_t = nil
-
@racc_val = nil
-
-
@racc_read_next = true
-
-
@racc_user_yyerror = false
-
@racc_error_status = 0
-
end
-
-
###
-
### do_parse
-
###
-
-
class_eval %{
-
def do_parse
-
#{Racc_Main_Parsing_Routine}(_racc_setup(), false)
-
end
-
1
}
-
-
1
def next_token
-
raise NotImplementedError, "#{self.class}\#next_token is not defined"
-
end
-
-
1
def _racc_do_parse_rb(arg, in_debug)
-
action_table, action_check, action_default, action_pointer,
-
_, _, _, _,
-
_, _, token_table, _,
-
_, _, * = arg
-
-
_racc_init_sysvars
-
tok = act = i = nil
-
-
catch(:racc_end_parse) {
-
while true
-
if i = action_pointer[@racc_state[-1]]
-
if @racc_read_next
-
if @racc_t != 0 # not EOF
-
tok, @racc_val = next_token()
-
unless tok # EOF
-
@racc_t = 0
-
else
-
@racc_t = (token_table[tok] or 1) # error token
-
end
-
racc_read_token(@racc_t, tok, @racc_val) if @yydebug
-
@racc_read_next = false
-
end
-
end
-
i += @racc_t
-
unless i >= 0 and
-
act = action_table[i] and
-
action_check[i] == @racc_state[-1]
-
act = action_default[@racc_state[-1]]
-
end
-
else
-
act = action_default[@racc_state[-1]]
-
end
-
while act = _racc_evalact(act, arg)
-
;
-
end
-
end
-
}
-
end
-
-
###
-
### yyparse
-
###
-
-
class_eval %{
-
def yyparse(recv, mid)
-
#{Racc_YY_Parse_Method}(recv, mid, _racc_setup(), true)
-
end
-
1
}
-
-
1
def _racc_yyparse_rb(recv, mid, arg, c_debug)
-
action_table, action_check, action_default, action_pointer,
-
_, _, _, _,
-
_, _, token_table, _,
-
_, _, * = arg
-
-
_racc_init_sysvars
-
act = nil
-
i = nil
-
-
catch(:racc_end_parse) {
-
until i = action_pointer[@racc_state[-1]]
-
while act = _racc_evalact(action_default[@racc_state[-1]], arg)
-
;
-
end
-
end
-
recv.__send__(mid) do |tok, val|
-
unless tok
-
@racc_t = 0
-
else
-
@racc_t = (token_table[tok] or 1) # error token
-
end
-
@racc_val = val
-
@racc_read_next = false
-
-
i += @racc_t
-
unless i >= 0 and
-
act = action_table[i] and
-
action_check[i] == @racc_state[-1]
-
act = action_default[@racc_state[-1]]
-
end
-
while act = _racc_evalact(act, arg)
-
;
-
end
-
-
while not(i = action_pointer[@racc_state[-1]]) or
-
not @racc_read_next or
-
@racc_t == 0 # $
-
unless i and i += @racc_t and
-
i >= 0 and
-
act = action_table[i] and
-
action_check[i] == @racc_state[-1]
-
act = action_default[@racc_state[-1]]
-
end
-
while act = _racc_evalact(act, arg)
-
;
-
end
-
end
-
end
-
}
-
end
-
-
###
-
### common
-
###
-
-
1
def _racc_evalact(act, arg)
-
action_table, action_check, _, action_pointer,
-
_, _, _, _,
-
_, _, _, shift_n, reduce_n,
-
_, _, * = arg
-
nerr = 0 # tmp
-
-
if act > 0 and act < shift_n
-
#
-
# shift
-
#
-
if @racc_error_status > 0
-
@racc_error_status -= 1 unless @racc_t == 1 # error token
-
end
-
@racc_vstack.push @racc_val
-
@racc_state.push act
-
@racc_read_next = true
-
if @yydebug
-
@racc_tstack.push @racc_t
-
racc_shift @racc_t, @racc_tstack, @racc_vstack
-
end
-
-
elsif act < 0 and act > -reduce_n
-
#
-
# reduce
-
#
-
code = catch(:racc_jump) {
-
@racc_state.push _racc_do_reduce(arg, act)
-
false
-
}
-
if code
-
case code
-
when 1 # yyerror
-
@racc_user_yyerror = true # user_yyerror
-
return -reduce_n
-
when 2 # yyaccept
-
return shift_n
-
else
-
raise '[Racc Bug] unknown jump code'
-
end
-
end
-
-
elsif act == shift_n
-
#
-
# accept
-
#
-
racc_accept if @yydebug
-
throw :racc_end_parse, @racc_vstack[0]
-
-
elsif act == -reduce_n
-
#
-
# error
-
#
-
case @racc_error_status
-
when 0
-
unless arg[21] # user_yyerror
-
nerr += 1
-
on_error @racc_t, @racc_val, @racc_vstack
-
end
-
when 3
-
if @racc_t == 0 # is $
-
throw :racc_end_parse, nil
-
end
-
@racc_read_next = true
-
end
-
@racc_user_yyerror = false
-
@racc_error_status = 3
-
while true
-
if i = action_pointer[@racc_state[-1]]
-
i += 1 # error token
-
if i >= 0 and
-
(act = action_table[i]) and
-
action_check[i] == @racc_state[-1]
-
break
-
end
-
end
-
throw :racc_end_parse, nil if @racc_state.size <= 1
-
@racc_state.pop
-
@racc_vstack.pop
-
if @yydebug
-
@racc_tstack.pop
-
racc_e_pop @racc_state, @racc_tstack, @racc_vstack
-
end
-
end
-
return act
-
-
else
-
raise "[Racc Bug] unknown action #{act.inspect}"
-
end
-
-
racc_next_state(@racc_state[-1], @racc_state) if @yydebug
-
-
nil
-
end
-
-
1
def _racc_do_reduce(arg, act)
-
_, _, _, _,
-
goto_table, goto_check, goto_default, goto_pointer,
-
nt_base, reduce_table, _, _,
-
_, use_result, * = arg
-
state = @racc_state
-
vstack = @racc_vstack
-
tstack = @racc_tstack
-
-
i = act * -3
-
len = reduce_table[i]
-
reduce_to = reduce_table[i+1]
-
method_id = reduce_table[i+2]
-
void_array = []
-
-
tmp_t = tstack[-len, len] if @yydebug
-
tmp_v = vstack[-len, len]
-
tstack[-len, len] = void_array if @yydebug
-
vstack[-len, len] = void_array
-
state[-len, len] = void_array
-
-
# tstack must be updated AFTER method call
-
if use_result
-
vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0])
-
else
-
vstack.push __send__(method_id, tmp_v, vstack)
-
end
-
tstack.push reduce_to
-
-
racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug
-
-
k1 = reduce_to - nt_base
-
if i = goto_pointer[k1]
-
i += state[-1]
-
if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1
-
return curstate
-
end
-
end
-
goto_default[k1]
-
end
-
-
1
def on_error(t, val, vstack)
-
raise ParseError, sprintf("\nparse error on value %s (%s)",
-
val.inspect, token_to_str(t) || '?')
-
end
-
-
1
def yyerror
-
throw :racc_jump, 1
-
end
-
-
1
def yyaccept
-
throw :racc_jump, 2
-
end
-
-
1
def yyerrok
-
@racc_error_status = 0
-
end
-
-
#
-
# for debugging output
-
#
-
-
1
def racc_read_token(t, tok, val)
-
@racc_debug_out.print 'read '
-
@racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') '
-
@racc_debug_out.puts val.inspect
-
@racc_debug_out.puts
-
end
-
-
1
def racc_shift(tok, tstack, vstack)
-
@racc_debug_out.puts "shift #{racc_token2str tok}"
-
racc_print_stacks tstack, vstack
-
@racc_debug_out.puts
-
end
-
-
1
def racc_reduce(toks, sim, tstack, vstack)
-
out = @racc_debug_out
-
out.print 'reduce '
-
if toks.empty?
-
out.print ' <none>'
-
else
-
toks.each {|t| out.print ' ', racc_token2str(t) }
-
end
-
out.puts " --> #{racc_token2str(sim)}"
-
-
racc_print_stacks tstack, vstack
-
@racc_debug_out.puts
-
end
-
-
1
def racc_accept
-
@racc_debug_out.puts 'accept'
-
@racc_debug_out.puts
-
end
-
-
1
def racc_e_pop(state, tstack, vstack)
-
@racc_debug_out.puts 'error recovering mode: pop token'
-
racc_print_states state
-
racc_print_stacks tstack, vstack
-
@racc_debug_out.puts
-
end
-
-
1
def racc_next_state(curstate, state)
-
@racc_debug_out.puts "goto #{curstate}"
-
racc_print_states state
-
@racc_debug_out.puts
-
end
-
-
1
def racc_print_stacks(t, v)
-
out = @racc_debug_out
-
out.print ' ['
-
t.each_index do |i|
-
out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')'
-
end
-
out.puts ' ]'
-
end
-
-
1
def racc_print_states(s)
-
out = @racc_debug_out
-
out.print ' ['
-
s.each {|st| out.print ' ', st }
-
out.puts ' ]'
-
end
-
-
1
def racc_token2str(tok)
-
self.class::Racc_token_to_s_table[tok] or
-
raise "[Racc Bug] can't convert token #{tok} to string"
-
end
-
-
1
def token_to_str(t)
-
self.class::Racc_token_to_s_table[t]
-
end
-
-
end
-
-
end
-
#vim:ts=2 sw=2 noexpandtab:
-
1
require 'rexml/child'
-
1
require 'rexml/source'
-
-
1
module REXML
-
# This class needs:
-
# * Documentation
-
# * Work! Not all types of attlists are intelligently parsed, so we just
-
# spew back out what we get in. This works, but it would be better if
-
# we formatted the output ourselves.
-
#
-
# AttlistDecls provide *just* enough support to allow namespace
-
# declarations. If you need some sort of generalized support, or have an
-
# interesting idea about how to map the hideous, terrible design of DTD
-
# AttlistDecls onto an intuitive Ruby interface, let me know. I'm desperate
-
# for anything to make DTDs more palateable.
-
1
class AttlistDecl < Child
-
1
include Enumerable
-
-
# What is this? Got me.
-
1
attr_reader :element_name
-
-
# Create an AttlistDecl, pulling the information from a Source. Notice
-
# that this isn't very convenient; to create an AttlistDecl, you basically
-
# have to format it yourself, and then have the initializer parse it.
-
# Sorry, but for the forseeable future, DTD support in REXML is pretty
-
# weak on convenience. Have I mentioned how much I hate DTDs?
-
1
def initialize(source)
-
super()
-
if (source.kind_of? Array)
-
@element_name, @pairs, @contents = *source
-
end
-
end
-
-
# Access the attlist attribute/value pairs.
-
# value = attlist_decl[ attribute_name ]
-
1
def [](key)
-
@pairs[key]
-
end
-
-
# Whether an attlist declaration includes the given attribute definition
-
# if attlist_decl.include? "xmlns:foobar"
-
1
def include?(key)
-
@pairs.keys.include? key
-
end
-
-
# Iterate over the key/value pairs:
-
# attlist_decl.each { |attribute_name, attribute_value| ... }
-
1
def each(&block)
-
@pairs.each(&block)
-
end
-
-
# Write out exactly what we got in.
-
1
def write out, indent=-1
-
out << @contents
-
end
-
-
1
def node_type
-
:attlistdecl
-
end
-
end
-
end
-
1
require "rexml/namespace"
-
1
require 'rexml/text'
-
-
1
module REXML
-
# Defines an Element Attribute; IE, a attribute=value pair, as in:
-
# <element attribute="value"/>. Attributes can be in their own
-
# namespaces. General users of REXML will not interact with the
-
# Attribute class much.
-
1
class Attribute
-
1
include Node
-
1
include Namespace
-
-
# The element to which this attribute belongs
-
1
attr_reader :element
-
# The normalized value of this attribute. That is, the attribute with
-
# entities intact.
-
1
attr_writer :normalized
-
1
PATTERN = /\s*(#{NAME_STR})\s*=\s*(["'])(.*?)\2/um
-
-
1
NEEDS_A_SECOND_CHECK = /(<|&((#{Entity::NAME});|(#0*((?:\d+)|(?:x[a-fA-F0-9]+)));)?)/um
-
-
# Constructor.
-
# FIXME: The parser doesn't catch illegal characters in attributes
-
#
-
# first::
-
# Either: an Attribute, which this new attribute will become a
-
# clone of; or a String, which is the name of this attribute
-
# second::
-
# If +first+ is an Attribute, then this may be an Element, or nil.
-
# If nil, then the Element parent of this attribute is the parent
-
# of the +first+ Attribute. If the first argument is a String,
-
# then this must also be a String, and is the content of the attribute.
-
# If this is the content, it must be fully normalized (contain no
-
# illegal characters).
-
# parent::
-
# Ignored unless +first+ is a String; otherwise, may be the Element
-
# parent of this attribute, or nil.
-
#
-
#
-
# Attribute.new( attribute_to_clone )
-
# Attribute.new( attribute_to_clone, parent_element )
-
# Attribute.new( "attr", "attr_value" )
-
# Attribute.new( "attr", "attr_value", parent_element )
-
1
def initialize( first, second=nil, parent=nil )
-
39
@normalized = @unnormalized = @element = nil
-
39
if first.kind_of? Attribute
-
self.name = first.expanded_name
-
@unnormalized = first.value
-
if second.kind_of? Element
-
@element = second
-
else
-
@element = first.element
-
end
-
39
elsif first.kind_of? String
-
39
@element = parent
-
39
self.name = first
-
39
@normalized = second.to_s
-
else
-
raise "illegal argument #{first.class.name} to Attribute constructor"
-
end
-
end
-
-
# Returns the namespace of the attribute.
-
#
-
# e = Element.new( "elns:myelement" )
-
# e.add_attribute( "nsa:a", "aval" )
-
# e.add_attribute( "b", "bval" )
-
# e.attributes.get_attribute( "a" ).prefix # -> "nsa"
-
# e.attributes.get_attribute( "b" ).prefix # -> "elns"
-
# a = Attribute.new( "x", "y" )
-
# a.prefix # -> ""
-
1
def prefix
-
pf = super
-
if pf == ""
-
pf = @element.prefix if @element
-
end
-
pf
-
end
-
-
# Returns the namespace URL, if defined, or nil otherwise
-
#
-
# e = Element.new("el")
-
# e.add_attributes({"xmlns:ns", "http://url"})
-
# e.namespace( "ns" ) # -> "http://url"
-
1
def namespace arg=nil
-
arg = prefix if arg.nil?
-
@element.namespace arg
-
end
-
-
# Returns true if other is an Attribute and has the same name and value,
-
# false otherwise.
-
1
def ==( other )
-
other.kind_of?(Attribute) and other.name==name and other.value==value
-
end
-
-
# Creates (and returns) a hash from both the name and value
-
1
def hash
-
name.hash + value.hash
-
end
-
-
# Returns this attribute out as XML source, expanding the name
-
#
-
# a = Attribute.new( "x", "y" )
-
# a.to_string # -> "x='y'"
-
# b = Attribute.new( "ns:x", "y" )
-
# b.to_string # -> "ns:x='y'"
-
1
def to_string
-
if @element and @element.context and @element.context[:attribute_quote] == :quote
-
%Q^#@expanded_name="#{to_s().gsub(/"/, '"e;')}"^
-
else
-
"#@expanded_name='#{to_s().gsub(/'/, ''')}'"
-
end
-
end
-
-
1
def doctype
-
72
if @element
-
72
doc = @element.document
-
72
doc.doctype if doc
-
end
-
end
-
-
# Returns the attribute value, with entities replaced
-
1
def to_s
-
return @normalized if @normalized
-
-
@normalized = Text::normalize( @unnormalized, doctype )
-
@unnormalized = nil
-
@normalized
-
end
-
-
# Returns the UNNORMALIZED value of this attribute. That is, entities
-
# have been expanded to their values
-
1
def value
-
33
return @unnormalized if @unnormalized
-
33
@unnormalized = Text::unnormalize( @normalized, doctype )
-
33
@normalized = nil
-
33
@unnormalized
-
end
-
-
# Returns a copy of this attribute
-
1
def clone
-
Attribute.new self
-
end
-
-
# Sets the element of which this object is an attribute. Normally, this
-
# is not directly called.
-
#
-
# Returns this attribute
-
1
def element=( element )
-
39
@element = element
-
-
39
if @normalized
-
39
Text.check( @normalized, NEEDS_A_SECOND_CHECK, doctype )
-
end
-
-
39
self
-
end
-
-
# Removes this Attribute from the tree, and returns true if successfull
-
#
-
# This method is usually not called directly.
-
1
def remove
-
@element.attributes.delete self.name unless @element.nil?
-
end
-
-
# Writes this attribute (EG, puts 'key="value"' to the output)
-
1
def write( output, indent=-1 )
-
output << to_string
-
end
-
-
1
def node_type
-
:attribute
-
end
-
-
1
def inspect
-
rv = ""
-
write( rv )
-
rv
-
end
-
-
1
def xpath
-
path = @element.xpath
-
path += "/@#{self.expanded_name}"
-
return path
-
end
-
end
-
end
-
#vim:ts=2 sw=2 noexpandtab:
-
1
require "rexml/text"
-
-
1
module REXML
-
1
class CData < Text
-
1
START = '<![CDATA['
-
1
STOP = ']]>'
-
1
ILLEGAL = /(\]\]>)/
-
-
# Constructor. CData is data between <![CDATA[ ... ]]>
-
#
-
# _Examples_
-
# CData.new( source )
-
# CData.new( "Here is some CDATA" )
-
# CData.new( "Some unprocessed data", respect_whitespace_TF, parent_element )
-
1
def initialize( first, whitespace=true, parent=nil )
-
super( first, whitespace, parent, false, true, ILLEGAL )
-
end
-
-
# Make a copy of this object
-
#
-
# _Examples_
-
# c = CData.new( "Some text" )
-
# d = c.clone
-
# d.to_s # -> "Some text"
-
1
def clone
-
CData.new self
-
end
-
-
# Returns the content of this CData object
-
#
-
# _Examples_
-
# c = CData.new( "Some text" )
-
# c.to_s # -> "Some text"
-
1
def to_s
-
@string
-
end
-
-
1
def value
-
@string
-
end
-
-
# == DEPRECATED
-
# See the rexml/formatters package
-
#
-
# Generates XML output of this object
-
#
-
# output::
-
# Where to write the string. Defaults to $stdout
-
# indent::
-
# The amount to indent this node by
-
# transitive::
-
# Ignored
-
# ie_hack::
-
# Ignored
-
#
-
# _Examples_
-
# c = CData.new( " Some text " )
-
# c.write( $stdout ) #-> <![CDATA[ Some text ]]>
-
1
def write( output=$stdout, indent=-1, transitive=false, ie_hack=false )
-
Kernel.warn( "#{self.class.name}.write is deprecated" )
-
indent( output, indent )
-
output << START
-
output << @string
-
output << STOP
-
end
-
end
-
end
-
1
require "rexml/node"
-
-
1
module REXML
-
##
-
# A Child object is something contained by a parent, and this class
-
# contains methods to support that. Most user code will not use this
-
# class directly.
-
1
class Child
-
1
include Node
-
1
attr_reader :parent # The Parent of this object
-
-
# Constructor. Any inheritors of this class should call super to make
-
# sure this method is called.
-
# parent::
-
# if supplied, the parent of this child will be set to the
-
# supplied value, and self will be added to the parent
-
1
def initialize( parent = nil )
-
93
@parent = nil
-
# Declare @parent, but don't define it. The next line sets the
-
# parent.
-
93
parent.add( self ) if parent
-
end
-
-
# Replaces this object with another object. Basically, calls
-
# Parent.replace_child
-
#
-
# Returns:: self
-
1
def replace_with( child )
-
@parent.replace_child( self, child )
-
self
-
end
-
-
# Removes this child from the parent.
-
#
-
# Returns:: self
-
1
def remove
-
unless @parent.nil?
-
@parent.delete self
-
end
-
self
-
end
-
-
# Sets the parent of this child to the supplied argument.
-
#
-
# other::
-
# Must be a Parent object. If this object is the same object as the
-
# existing parent of this child, no action is taken. Otherwise, this
-
# child is removed from the current parent (if one exists), and is added
-
# to the new parent.
-
# Returns:: The parent added
-
1
def parent=( other )
-
137
return @parent if @parent == other
-
137
@parent.delete self if defined? @parent and @parent
-
137
@parent = other
-
end
-
-
1
alias :next_sibling :next_sibling_node
-
1
alias :previous_sibling :previous_sibling_node
-
-
# Sets the next sibling of this child. This can be used to insert a child
-
# after some other child.
-
# a = Element.new("a")
-
# b = a.add_element("b")
-
# c = Element.new("c")
-
# b.next_sibling = c
-
# # => <a><b/><c/></a>
-
1
def next_sibling=( other )
-
parent.insert_after self, other
-
end
-
-
# Sets the previous sibling of this child. This can be used to insert a
-
# child before some other child.
-
# a = Element.new("a")
-
# b = a.add_element("b")
-
# c = Element.new("c")
-
# b.previous_sibling = c
-
# # => <a><b/><c/></a>
-
1
def previous_sibling=(other)
-
parent.insert_before self, other
-
end
-
-
# Returns:: the document this child belongs to, or nil if this child
-
# belongs to no document
-
1
def document
-
return parent.document unless parent.nil?
-
nil
-
end
-
-
# This doesn't yet handle encodings
-
1
def bytes
-
document.encoding
-
-
to_s
-
end
-
end
-
end
-
1
require "rexml/child"
-
-
1
module REXML
-
##
-
# Represents an XML comment; that is, text between \<!-- ... -->
-
1
class Comment < Child
-
1
include Comparable
-
1
START = "<!--"
-
1
STOP = "-->"
-
-
# The content text
-
-
1
attr_accessor :string
-
-
##
-
# Constructor. The first argument can be one of three types:
-
# @param first If String, the contents of this comment are set to the
-
# argument. If Comment, the argument is duplicated. If
-
# Source, the argument is scanned for a comment.
-
# @param second If the first argument is a Source, this argument
-
# should be nil, not supplied, or a Parent to be set as the parent
-
# of this object
-
1
def initialize( first, second = nil )
-
#puts "IN COMMENT CONSTRUCTOR; SECOND IS #{second.type}"
-
super(second)
-
if first.kind_of? String
-
@string = first
-
elsif first.kind_of? Comment
-
@string = first.string
-
end
-
end
-
-
1
def clone
-
Comment.new self
-
end
-
-
# == DEPRECATED
-
# See REXML::Formatters
-
#
-
# output::
-
# Where to write the string
-
# indent::
-
# An integer. If -1, no indenting will be used; otherwise, the
-
# indentation will be this number of spaces, and children will be
-
# indented an additional amount.
-
# transitive::
-
# Ignored by this class. The contents of comments are never modified.
-
# ie_hack::
-
# Needed for conformity to the child API, but not used by this class.
-
1
def write( output, indent=-1, transitive=false, ie_hack=false )
-
Kernel.warn("Comment.write is deprecated. See REXML::Formatters")
-
indent( output, indent )
-
output << START
-
output << @string
-
output << STOP
-
end
-
-
1
alias :to_s :string
-
-
##
-
# Compares this Comment to another; the contents of the comment are used
-
# in the comparison.
-
1
def <=>(other)
-
other.to_s <=> @string
-
end
-
-
##
-
# Compares this Comment to another; the contents of the comment are used
-
# in the comparison.
-
1
def ==( other )
-
other.kind_of? Comment and
-
(other <=> self) == 0
-
end
-
-
1
def node_type
-
:comment
-
end
-
end
-
end
-
#vim:ts=2 sw=2 noexpandtab:
-
1
require "rexml/parent"
-
1
require "rexml/parseexception"
-
1
require "rexml/namespace"
-
1
require 'rexml/entity'
-
1
require 'rexml/attlistdecl'
-
1
require 'rexml/xmltokens'
-
-
1
module REXML
-
# Represents an XML DOCTYPE declaration; that is, the contents of <!DOCTYPE
-
# ... >. DOCTYPES can be used to declare the DTD of a document, as well as
-
# being used to declare entities used in the document.
-
1
class DocType < Parent
-
1
include XMLTokens
-
1
START = "<!DOCTYPE"
-
1
STOP = ">"
-
1
SYSTEM = "SYSTEM"
-
1
PUBLIC = "PUBLIC"
-
1
DEFAULT_ENTITIES = {
-
'gt'=>EntityConst::GT,
-
'lt'=>EntityConst::LT,
-
'quot'=>EntityConst::QUOT,
-
"apos"=>EntityConst::APOS
-
}
-
-
# name is the name of the doctype
-
# external_id is the referenced DTD, if given
-
1
attr_reader :name, :external_id, :entities, :namespaces
-
-
# Constructor
-
#
-
# dt = DocType.new( 'foo', '-//I/Hate/External/IDs' )
-
# # <!DOCTYPE foo '-//I/Hate/External/IDs'>
-
# dt = DocType.new( doctype_to_clone )
-
# # Incomplete. Shallow clone of doctype
-
#
-
# +Note+ that the constructor:
-
#
-
# Doctype.new( Source.new( "<!DOCTYPE foo 'bar'>" ) )
-
#
-
# is _deprecated_. Do not use it. It will probably disappear.
-
1
def initialize( first, parent=nil )
-
@entities = DEFAULT_ENTITIES
-
@long_name = @uri = nil
-
if first.kind_of? String
-
super()
-
@name = first
-
@external_id = parent
-
elsif first.kind_of? DocType
-
super( parent )
-
@name = first.name
-
@external_id = first.external_id
-
elsif first.kind_of? Array
-
super( parent )
-
@name = first[0]
-
@external_id = first[1]
-
@long_name = first[2]
-
@uri = first[3]
-
elsif first.kind_of? Source
-
super( parent )
-
parser = Parsers::BaseParser.new( first )
-
event = parser.pull
-
if event[0] == :start_doctype
-
@name, @external_id, @long_name, @uri, = event[1..-1]
-
end
-
else
-
super()
-
end
-
end
-
-
1
def node_type
-
:doctype
-
end
-
-
1
def attributes_of element
-
rv = []
-
each do |child|
-
child.each do |key,val|
-
rv << Attribute.new(key,val)
-
end if child.kind_of? AttlistDecl and child.element_name == element
-
end
-
rv
-
end
-
-
1
def attribute_of element, attribute
-
att_decl = find do |child|
-
child.kind_of? AttlistDecl and
-
child.element_name == element and
-
child.include? attribute
-
end
-
return nil unless att_decl
-
att_decl[attribute]
-
end
-
-
1
def clone
-
DocType.new self
-
end
-
-
# output::
-
# Where to write the string
-
# indent::
-
# An integer. If -1, no indentation will be used; otherwise, the
-
# indentation will be this number of spaces, and children will be
-
# indented an additional amount.
-
# transitive::
-
# Ignored
-
# ie_hack::
-
# Ignored
-
1
def write( output, indent=0, transitive=false, ie_hack=false )
-
f = REXML::Formatters::Default.new
-
indent( output, indent )
-
output << START
-
output << ' '
-
output << @name
-
output << " #@external_id" if @external_id
-
output << " #{@long_name.inspect}" if @long_name
-
output << " #{@uri.inspect}" if @uri
-
unless @children.empty?
-
output << ' ['
-
@children.each { |child|
-
output << "\n"
-
f.write( child, output )
-
}
-
output << "\n]"
-
end
-
output << STOP
-
end
-
-
1
def context
-
@parent.context
-
end
-
-
1
def entity( name )
-
@entities[name].unnormalized if @entities[name]
-
end
-
-
1
def add child
-
super(child)
-
@entities = DEFAULT_ENTITIES.clone if @entities == DEFAULT_ENTITIES
-
@entities[ child.name ] = child if child.kind_of? Entity
-
end
-
-
# This method retrieves the public identifier identifying the document's
-
# DTD.
-
#
-
# Method contributed by Henrik Martensson
-
1
def public
-
case @external_id
-
when "SYSTEM"
-
nil
-
when "PUBLIC"
-
strip_quotes(@long_name)
-
end
-
end
-
-
# This method retrieves the system identifier identifying the document's DTD
-
#
-
# Method contributed by Henrik Martensson
-
1
def system
-
case @external_id
-
when "SYSTEM"
-
strip_quotes(@long_name)
-
when "PUBLIC"
-
@uri.kind_of?(String) ? strip_quotes(@uri) : nil
-
end
-
end
-
-
# This method returns a list of notations that have been declared in the
-
# _internal_ DTD subset. Notations in the external DTD subset are not
-
# listed.
-
#
-
# Method contributed by Henrik Martensson
-
1
def notations
-
children().select {|node| node.kind_of?(REXML::NotationDecl)}
-
end
-
-
# Retrieves a named notation. Only notations declared in the internal
-
# DTD subset can be retrieved.
-
#
-
# Method contributed by Henrik Martensson
-
1
def notation(name)
-
notations.find { |notation_decl|
-
notation_decl.name == name
-
}
-
end
-
-
1
private
-
-
# Method contributed by Henrik Martensson
-
1
def strip_quotes(quoted_string)
-
quoted_string =~ /^[\'\"].*[\'\"]$/ ?
-
quoted_string[1, quoted_string.length-2] :
-
quoted_string
-
end
-
end
-
-
# We don't really handle any of these since we're not a validating
-
# parser, so we can be pretty dumb about them. All we need to be able
-
# to do is spew them back out on a write()
-
-
# This is an abstract class. You never use this directly; it serves as a
-
# parent class for the specific declarations.
-
1
class Declaration < Child
-
1
def initialize src
-
super()
-
@string = src
-
end
-
-
1
def to_s
-
@string+'>'
-
end
-
-
# == DEPRECATED
-
# See REXML::Formatters
-
#
-
1
def write( output, indent )
-
output << to_s
-
end
-
end
-
-
1
public
-
1
class ElementDecl < Declaration
-
1
def initialize( src )
-
super
-
end
-
end
-
-
1
class ExternalEntity < Child
-
1
def initialize( src )
-
super()
-
@entity = src
-
end
-
1
def to_s
-
@entity
-
end
-
1
def write( output, indent )
-
output << @entity
-
end
-
end
-
-
1
class NotationDecl < Child
-
1
attr_accessor :public, :system
-
1
def initialize name, middle, pub, sys
-
super(nil)
-
@name = name
-
@middle = middle
-
@public = pub
-
@system = sys
-
end
-
-
1
def to_s
-
notation = "<!NOTATION #{@name} #{@middle}"
-
notation << " #{@public.inspect}" if @public
-
notation << " #{@system.inspect}" if @system
-
notation << ">"
-
notation
-
end
-
-
1
def write( output, indent=-1 )
-
output << to_s
-
end
-
-
# This method retrieves the name of the notation.
-
#
-
# Method contributed by Henrik Martensson
-
1
def name
-
@name
-
end
-
end
-
end
-
1
require "rexml/element"
-
1
require "rexml/xmldecl"
-
1
require "rexml/source"
-
1
require "rexml/comment"
-
1
require "rexml/doctype"
-
1
require "rexml/instruction"
-
1
require "rexml/rexml"
-
1
require "rexml/parseexception"
-
1
require "rexml/output"
-
1
require "rexml/parsers/baseparser"
-
1
require "rexml/parsers/streamparser"
-
1
require "rexml/parsers/treeparser"
-
-
1
module REXML
-
# Represents a full XML document, including PIs, a doctype, etc. A
-
# Document has a single child that can be accessed by root().
-
# Note that if you want to have an XML declaration written for a document
-
# you create, you must add one; REXML documents do not write a default
-
# declaration for you. See |DECLARATION| and |write|.
-
1
class Document < Element
-
# A convenient default XML declaration. If you want an XML declaration,
-
# the easiest way to add one is mydoc << Document::DECLARATION
-
# +DEPRECATED+
-
# Use: mydoc << XMLDecl.default
-
1
DECLARATION = XMLDecl.default
-
-
# Constructor
-
# @param source if supplied, must be a Document, String, or IO.
-
# Documents have their context and Element attributes cloned.
-
# Strings are expected to be valid XML documents. IOs are expected
-
# to be sources of valid XML documents.
-
# @param context if supplied, contains the context of the document;
-
# this should be a Hash.
-
1
def initialize( source = nil, context = {} )
-
24
@entity_expansion_count = 0
-
24
super()
-
24
@context = context
-
24
return if source.nil?
-
24
if source.kind_of? Document
-
@context = source.context
-
super source
-
else
-
24
build( source )
-
end
-
end
-
-
1
def node_type
-
:document
-
end
-
-
# Should be obvious
-
1
def clone
-
Document.new self
-
end
-
-
# According to the XML spec, a root node has no expanded name
-
1
def expanded_name
-
''
-
#d = doc_type
-
#d ? d.name : "UNDEFINED"
-
end
-
-
1
alias :name :expanded_name
-
-
# We override this, because XMLDecls and DocTypes must go at the start
-
# of the document
-
1
def add( child )
-
32
if child.kind_of? XMLDecl
-
if @children[0].kind_of? XMLDecl
-
@children[0] = child
-
else
-
@children.unshift child
-
end
-
child.parent = self
-
32
elsif child.kind_of? DocType
-
# Find first Element or DocType node and insert the decl right
-
# before it. If there is no such node, just insert the child at the
-
# end. If there is a child and it is an DocType, then replace it.
-
insert_before_index = @children.find_index { |x|
-
x.kind_of?(Element) || x.kind_of?(DocType)
-
}
-
if insert_before_index # Not null = not end of list
-
if @children[ insert_before_index ].kind_of? DocType
-
@children[ insert_before_index ] = child
-
else
-
@children[ insert_before_index-1, 0 ] = child
-
end
-
else # Insert at end of list
-
@children << child
-
end
-
child.parent = self
-
else
-
32
rv = super
-
32
raise "attempted adding second root element to document" if @elements.size > 1
-
32
rv
-
end
-
end
-
1
alias :<< :add
-
-
1
def add_element(arg=nil, arg2=nil)
-
24
rv = super
-
24
raise "attempted adding second root element to document" if @elements.size > 1
-
24
rv
-
end
-
-
# @return the root Element of the document, or nil if this document
-
# has no children.
-
1
def root
-
48
elements[1]
-
#self
-
#@children.find { |item| item.kind_of? Element }
-
end
-
-
# @return the DocType child of the document, if one exists,
-
# and nil otherwise.
-
1
def doctype
-
487
@children.find { |item| item.kind_of? DocType }
-
end
-
-
# @return the XMLDecl of this document; if no XMLDecl has been
-
# set, the default declaration is returned.
-
1
def xml_decl
-
rv = @children[0]
-
return rv if rv.kind_of? XMLDecl
-
rv = @children.unshift(XMLDecl.default)[0]
-
end
-
-
# @return the XMLDecl version of this document as a String.
-
# If no XMLDecl has been set, returns the default version.
-
1
def version
-
xml_decl().version
-
end
-
-
# @return the XMLDecl encoding of this document as an
-
# Encoding object.
-
# If no XMLDecl has been set, returns the default encoding.
-
1
def encoding
-
xml_decl().encoding
-
end
-
-
# @return the XMLDecl standalone value of this document as a String.
-
# If no XMLDecl has been set, returns the default setting.
-
1
def stand_alone?
-
xml_decl().stand_alone?
-
end
-
-
# Write the XML tree out, optionally with indent. This writes out the
-
# entire XML document, including XML declarations, doctype declarations,
-
# and processing instructions (if any are given).
-
#
-
# A controversial point is whether Document should always write the XML
-
# declaration (<?xml version='1.0'?>) whether or not one is given by the
-
# user (or source document). REXML does not write one if one was not
-
# specified, because it adds unnecessary bandwidth to applications such
-
# as XML-RPC.
-
#
-
# See also the classes in the rexml/formatters package for the proper way
-
# to change the default formatting of XML output
-
#
-
# _Examples_
-
# Document.new("<a><b/></a>").serialize
-
#
-
# output_string = ""
-
# tr = Transitive.new( output_string )
-
# Document.new("<a><b/></a>").serialize( tr )
-
#
-
# output::
-
# output an object which supports '<< string'; this is where the
-
# document will be written.
-
# indent::
-
# An integer. If -1, no indenting will be used; otherwise, the
-
# indentation will be twice this number of spaces, and children will be
-
# indented an additional amount. For a value of 3, every item will be
-
# indented 3 more levels, or 6 more spaces (2 * 3). Defaults to -1
-
# transitive::
-
# If transitive is true and indent is >= 0, then the output will be
-
# pretty-printed in such a way that the added whitespace does not affect
-
# the absolute *value* of the document -- that is, it leaves the value
-
# and number of Text nodes in the document unchanged.
-
# ie_hack::
-
# Internet Explorer is the worst piece of crap to have ever been
-
# written, with the possible exception of Windows itself. Since IE is
-
# unable to parse proper XML, we have to provide a hack to generate XML
-
# that IE's limited abilities can handle. This hack inserts a space
-
# before the /> on empty tags. Defaults to false
-
1
def write( output=$stdout, indent=-1, transitive=false, ie_hack=false )
-
if xml_decl.encoding != 'UTF-8' && !output.kind_of?(Output)
-
output = Output.new( output, xml_decl.encoding )
-
end
-
formatter = if indent > -1
-
if transitive
-
require "rexml/formatters/transitive"
-
REXML::Formatters::Transitive.new( indent, ie_hack )
-
else
-
REXML::Formatters::Pretty.new( indent, ie_hack )
-
end
-
else
-
REXML::Formatters::Default.new( ie_hack )
-
end
-
formatter.write( self, output )
-
end
-
-
-
1
def Document::parse_stream( source, listener )
-
Parsers::StreamParser.new( source, listener ).parse
-
end
-
-
1
@@entity_expansion_limit = 10_000
-
-
# Set the entity expansion limit. By default the limit is set to 10000.
-
1
def Document::entity_expansion_limit=( val )
-
@@entity_expansion_limit = val
-
end
-
-
# Get the entity expansion limit. By default the limit is set to 10000.
-
1
def Document::entity_expansion_limit
-
return @@entity_expansion_limit
-
end
-
-
1
attr_reader :entity_expansion_count
-
-
1
def record_entity_expansion
-
@entity_expansion_count += 1
-
if @entity_expansion_count > @@entity_expansion_limit
-
raise "number of entity expansions exceeded, processing aborted."
-
end
-
end
-
-
1
private
-
1
def build( source )
-
24
Parsers::TreeParser.new( source, self ).parse
-
end
-
end
-
end
-
1
require "rexml/parent"
-
1
require "rexml/namespace"
-
1
require "rexml/attribute"
-
1
require "rexml/cdata"
-
1
require "rexml/xpath"
-
1
require "rexml/parseexception"
-
-
1
module REXML
-
# An implementation note about namespaces:
-
# As we parse, when we find namespaces we put them in a hash and assign
-
# them a unique ID. We then convert the namespace prefix for the node
-
# to the unique ID. This makes namespace lookup much faster for the
-
# cost of extra memory use. We save the namespace prefix for the
-
# context node and convert it back when we write it.
-
1
@@namespaces = {}
-
-
# Represents a tagged XML element. Elements are characterized by
-
# having children, attributes, and names, and can themselves be
-
# children.
-
1
class Element < Parent
-
1
include Namespace
-
-
1
UNDEFINED = "UNDEFINED"; # The default name
-
-
# Mechanisms for accessing attributes and child elements of this
-
# element.
-
1
attr_reader :attributes, :elements
-
# The context holds information about the processing environment, such as
-
# whitespace handling.
-
1
attr_accessor :context
-
-
# Constructor
-
# arg::
-
# if not supplied, will be set to the default value.
-
# If a String, the name of this object will be set to the argument.
-
# If an Element, the object will be shallowly cloned; name,
-
# attributes, and namespaces will be copied. Children will +not+ be
-
# copied.
-
# parent::
-
# if supplied, must be a Parent, and will be used as
-
# the parent of this object.
-
# context::
-
# If supplied, must be a hash containing context items. Context items
-
# include:
-
# * <tt>:respect_whitespace</tt> the value of this is :+all+ or an array of
-
# strings being the names of the elements to respect
-
# whitespace for. Defaults to :+all+.
-
# * <tt>:compress_whitespace</tt> the value can be :+all+ or an array of
-
# strings being the names of the elements to ignore whitespace on.
-
# Overrides :+respect_whitespace+.
-
# * <tt>:ignore_whitespace_nodes</tt> the value can be :+all+ or an array
-
# of strings being the names of the elements in which to ignore
-
# whitespace-only nodes. If this is set, Text nodes which contain only
-
# whitespace will not be added to the document tree.
-
# * <tt>:raw</tt> can be :+all+, or an array of strings being the names of
-
# the elements to process in raw mode. In raw mode, special
-
# characters in text is not converted to or from entities.
-
1
def initialize( arg = UNDEFINED, parent=nil, context=nil )
-
87
super(parent)
-
-
87
@elements = Elements.new(self)
-
87
@attributes = Attributes.new(self)
-
87
@context = context
-
-
87
if arg.kind_of? String
-
87
self.name = arg
-
elsif arg.kind_of? Element
-
self.name = arg.expanded_name
-
arg.attributes.each_attribute{ |attribute|
-
@attributes << Attribute.new( attribute )
-
}
-
@context = arg.context
-
end
-
end
-
-
1
def inspect
-
rv = "<#@expanded_name"
-
-
@attributes.each_attribute do |attr|
-
rv << " "
-
attr.write( rv, 0 )
-
end
-
-
if children.size > 0
-
rv << "> ... </>"
-
else
-
rv << "/>"
-
end
-
end
-
-
-
# Creates a shallow copy of self.
-
# d = Document.new "<a><b/><b/><c><d/></c></a>"
-
# new_a = d.root.clone
-
# puts new_a # => "<a/>"
-
1
def clone
-
self.class.new self
-
end
-
-
# Evaluates to the root node of the document that this element
-
# belongs to. If this element doesn't belong to a document, but does
-
# belong to another Element, the parent's root will be returned, until the
-
# earliest ancestor is found.
-
#
-
# Note that this is not the same as the document element.
-
# In the following example, <a> is the document element, and the root
-
# node is the parent node of the document element. You may ask yourself
-
# why the root node is useful: consider the doctype and XML declaration,
-
# and any processing instructions before the document element... they
-
# are children of the root node, or siblings of the document element.
-
# The only time this isn't true is when an Element is created that is
-
# not part of any Document. In this case, the ancestor that has no
-
# parent acts as the root node.
-
# d = Document.new '<a><b><c/></b></a>'
-
# a = d[1] ; c = a[1][1]
-
# d.root_node == d # TRUE
-
# a.root_node # namely, d
-
# c.root_node # again, d
-
1
def root_node
-
parent.nil? ? self : parent.root_node
-
end
-
-
1
def root
-
340
return elements[1] if self.kind_of? Document
-
340
return self if parent.kind_of? Document or parent.nil?
-
166
return parent.root
-
end
-
-
# Evaluates to the document to which this element belongs, or nil if this
-
# element doesn't belong to a document.
-
1
def document
-
182
rt = root
-
182
rt.parent if rt
-
end
-
-
# Evaluates to +true+ if whitespace is respected for this element. This
-
# is the case if:
-
# 1. Neither :+respect_whitespace+ nor :+compress_whitespace+ has any value
-
# 2. The context has :+respect_whitespace+ set to :+all+ or
-
# an array containing the name of this element, and
-
# :+compress_whitespace+ isn't set to :+all+ or an array containing the
-
# name of this element.
-
# The evaluation is tested against +expanded_name+, and so is namespace
-
# sensitive.
-
1
def whitespace
-
74
@whitespace = nil
-
74
if @context
-
74
if @context[:respect_whitespace]
-
@whitespace = (@context[:respect_whitespace] == :all or
-
@context[:respect_whitespace].include? expanded_name)
-
end
-
@whitespace = false if (@context[:compress_whitespace] and
-
(@context[:compress_whitespace] == :all or
-
74
@context[:compress_whitespace].include? expanded_name)
-
)
-
end
-
74
@whitespace = true unless @whitespace == false
-
74
@whitespace
-
end
-
-
1
def ignore_whitespace_nodes
-
74
@ignore_whitespace_nodes = false
-
74
if @context
-
74
if @context[:ignore_whitespace_nodes]
-
@ignore_whitespace_nodes =
-
(@context[:ignore_whitespace_nodes] == :all or
-
@context[:ignore_whitespace_nodes].include? expanded_name)
-
end
-
end
-
end
-
-
# Evaluates to +true+ if raw mode is set for this element. This
-
# is the case if the context has :+raw+ set to :+all+ or
-
# an array containing the name of this element.
-
#
-
# The evaluation is tested against +expanded_name+, and so is namespace
-
# sensitive.
-
1
def raw
-
@raw = (@context and @context[:raw] and
-
(@context[:raw] == :all or
-
@context[:raw].include? expanded_name))
-
@raw
-
end
-
-
#once :whitespace, :raw, :ignore_whitespace_nodes
-
-
#################################################
-
# Namespaces #
-
#################################################
-
-
# Evaluates to an +Array+ containing the prefixes (names) of all defined
-
# namespaces at this context node.
-
# doc = Document.new("<a xmlns:x='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
-
# doc.elements['//b'].prefixes # -> ['x', 'y']
-
1
def prefixes
-
prefixes = []
-
prefixes = parent.prefixes if parent
-
prefixes |= attributes.prefixes
-
return prefixes
-
end
-
-
1
def namespaces
-
namespaces = {}
-
namespaces = parent.namespaces if parent
-
namespaces = namespaces.merge( attributes.namespaces )
-
return namespaces
-
end
-
-
# Evalutas to the URI for a prefix, or the empty string if no such
-
# namespace is declared for this element. Evaluates recursively for
-
# ancestors. Returns the default namespace, if there is one.
-
# prefix::
-
# the prefix to search for. If not supplied, returns the default
-
# namespace if one exists
-
# Returns::
-
# the namespace URI as a String, or nil if no such namespace
-
# exists. If the namespace is undefined, returns an empty string
-
# doc = Document.new("<a xmlns='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
-
# b = doc.elements['//b']
-
# b.namespace # -> '1'
-
# b.namespace("y") # -> '2'
-
1
def namespace(prefix=nil)
-
if prefix.nil?
-
prefix = prefix()
-
end
-
if prefix == ''
-
prefix = "xmlns"
-
else
-
prefix = "xmlns:#{prefix}" unless prefix[0,5] == 'xmlns'
-
end
-
ns = attributes[ prefix ]
-
ns = parent.namespace(prefix) if ns.nil? and parent
-
ns = '' if ns.nil? and prefix == 'xmlns'
-
return ns
-
end
-
-
# Adds a namespace to this element.
-
# prefix::
-
# the prefix string, or the namespace URI if +uri+ is not
-
# supplied
-
# uri::
-
# the namespace URI. May be nil, in which +prefix+ is used as
-
# the URI
-
# Evaluates to: this Element
-
# a = Element.new("a")
-
# a.add_namespace("xmlns:foo", "bar" )
-
# a.add_namespace("foo", "bar") # shorthand for previous line
-
# a.add_namespace("twiddle")
-
# puts a #-> <a xmlns:foo='bar' xmlns='twiddle'/>
-
1
def add_namespace( prefix, uri=nil )
-
unless uri
-
@attributes["xmlns"] = prefix
-
else
-
prefix = "xmlns:#{prefix}" unless prefix =~ /^xmlns:/
-
@attributes[ prefix ] = uri
-
end
-
self
-
end
-
-
# Removes a namespace from this node. This only works if the namespace is
-
# actually declared in this node. If no argument is passed, deletes the
-
# default namespace.
-
#
-
# Evaluates to: this element
-
# doc = Document.new "<a xmlns:foo='bar' xmlns='twiddle'/>"
-
# doc.root.delete_namespace
-
# puts doc # -> <a xmlns:foo='bar'/>
-
# doc.root.delete_namespace 'foo'
-
# puts doc # -> <a/>
-
1
def delete_namespace namespace="xmlns"
-
namespace = "xmlns:#{namespace}" unless namespace == 'xmlns'
-
attribute = attributes.get_attribute(namespace)
-
attribute.remove unless attribute.nil?
-
self
-
end
-
-
#################################################
-
# Elements #
-
#################################################
-
-
# Adds a child to this element, optionally setting attributes in
-
# the element.
-
# element::
-
# optional. If Element, the element is added.
-
# Otherwise, a new Element is constructed with the argument (see
-
# Element.initialize).
-
# attrs::
-
# If supplied, must be a Hash containing String name,value
-
# pairs, which will be used to set the attributes of the new Element.
-
# Returns:: the Element that was added
-
# el = doc.add_element 'my-tag'
-
# el = doc.add_element 'my-tag', {'attr1'=>'val1', 'attr2'=>'val2'}
-
# el = Element.new 'my-tag'
-
# doc.add_element el
-
1
def add_element element, attrs=nil
-
63
raise "First argument must be either an element name, or an Element object" if element.nil?
-
63
el = @elements.add(element)
-
attrs.each do |key, value|
-
el.attributes[key]=value
-
63
end if attrs.kind_of? Hash
-
63
el
-
end
-
-
# Deletes a child element.
-
# element::
-
# Must be an +Element+, +String+, or +Integer+. If Element,
-
# the element is removed. If String, the element is found (via XPath)
-
# and removed. <em>This means that any parent can remove any
-
# descendant.<em> If Integer, the Element indexed by that number will be
-
# removed.
-
# Returns:: the element that was removed.
-
# doc.delete_element "/a/b/c[@id='4']"
-
# doc.delete_element doc.elements["//k"]
-
# doc.delete_element 1
-
1
def delete_element element
-
@elements.delete element
-
end
-
-
# Evaluates to +true+ if this element has at least one child Element
-
# doc = Document.new "<a><b/><c>Text</c></a>"
-
# doc.root.has_elements # -> true
-
# doc.elements["/a/b"].has_elements # -> false
-
# doc.elements["/a/c"].has_elements # -> false
-
1
def has_elements?
-
53
!@elements.empty?
-
end
-
-
# Iterates through the child elements, yielding for each Element that
-
# has a particular attribute set.
-
# key::
-
# the name of the attribute to search for
-
# value::
-
# the value of the attribute
-
# max::
-
# (optional) causes this method to return after yielding
-
# for this number of matching children
-
# name::
-
# (optional) if supplied, this is an XPath that filters
-
# the children to check.
-
#
-
# doc = Document.new "<a><b @id='1'/><c @id='2'/><d @id='1'/><e/></a>"
-
# # Yields b, c, d
-
# doc.root.each_element_with_attribute( 'id' ) {|e| p e}
-
# # Yields b, d
-
# doc.root.each_element_with_attribute( 'id', '1' ) {|e| p e}
-
# # Yields b
-
# doc.root.each_element_with_attribute( 'id', '1', 1 ) {|e| p e}
-
# # Yields d
-
# doc.root.each_element_with_attribute( 'id', '1', 0, 'd' ) {|e| p e}
-
1
def each_element_with_attribute( key, value=nil, max=0, name=nil, &block ) # :yields: Element
-
each_with_something( proc {|child|
-
if value.nil?
-
child.attributes[key] != nil
-
else
-
child.attributes[key]==value
-
end
-
}, max, name, &block )
-
end
-
-
# Iterates through the children, yielding for each Element that
-
# has a particular text set.
-
# text::
-
# the text to search for. If nil, or not supplied, will iterate
-
# over all +Element+ children that contain at least one +Text+ node.
-
# max::
-
# (optional) causes this method to return after yielding
-
# for this number of matching children
-
# name::
-
# (optional) if supplied, this is an XPath that filters
-
# the children to check.
-
#
-
# doc = Document.new '<a><b>b</b><c>b</c><d>d</d><e/></a>'
-
# # Yields b, c, d
-
# doc.each_element_with_text {|e|p e}
-
# # Yields b, c
-
# doc.each_element_with_text('b'){|e|p e}
-
# # Yields b
-
# doc.each_element_with_text('b', 1){|e|p e}
-
# # Yields d
-
# doc.each_element_with_text(nil, 0, 'd'){|e|p e}
-
1
def each_element_with_text( text=nil, max=0, name=nil, &block ) # :yields: Element
-
each_with_something( proc {|child|
-
if text.nil?
-
child.has_text?
-
else
-
child.text == text
-
end
-
}, max, name, &block )
-
end
-
-
# Synonym for Element.elements.each
-
1
def each_element( xpath=nil, &block ) # :yields: Element
-
17
@elements.each( xpath, &block )
-
end
-
-
# Synonym for Element.to_a
-
# This is a little slower than calling elements.each directly.
-
# xpath:: any XPath by which to search for elements in the tree
-
# Returns:: an array of Elements that match the supplied path
-
1
def get_elements( xpath )
-
@elements.to_a( xpath )
-
end
-
-
# Returns the next sibling that is an element, or nil if there is
-
# no Element sibling after this one
-
# doc = Document.new '<a><b/>text<c/></a>'
-
# doc.root.elements['b'].next_element #-> <c/>
-
# doc.root.elements['c'].next_element #-> nil
-
1
def next_element
-
element = next_sibling
-
element = element.next_sibling until element.nil? or element.kind_of? Element
-
return element
-
end
-
-
# Returns the previous sibling that is an element, or nil if there is
-
# no Element sibling prior to this one
-
# doc = Document.new '<a><b/>text<c/></a>'
-
# doc.root.elements['c'].previous_element #-> <b/>
-
# doc.root.elements['b'].previous_element #-> nil
-
1
def previous_element
-
element = previous_sibling
-
element = element.previous_sibling until element.nil? or element.kind_of? Element
-
return element
-
end
-
-
-
#################################################
-
# Text #
-
#################################################
-
-
# Evaluates to +true+ if this element has at least one Text child
-
1
def has_text?
-
36
not text().nil?
-
end
-
-
# A convenience method which returns the String value of the _first_
-
# child text element, if one exists, and +nil+ otherwise.
-
#
-
# <em>Note that an element may have multiple Text elements, perhaps
-
# separated by other children</em>. Be aware that this method only returns
-
# the first Text node.
-
#
-
# This method returns the +value+ of the first text child node, which
-
# ignores the +raw+ setting, so always returns normalized text. See
-
# the Text::value documentation.
-
#
-
# doc = Document.new "<p>some text <b>this is bold!</b> more text</p>"
-
# # The element 'p' has two text elements, "some text " and " more text".
-
# doc.root.text #-> "some text "
-
1
def text( path = nil )
-
36
rv = get_text(path)
-
36
return rv.value unless rv.nil?
-
nil
-
end
-
-
# Returns the first child Text node, if any, or +nil+ otherwise.
-
# This method returns the actual +Text+ node, rather than the String content.
-
# doc = Document.new "<p>some text <b>this is bold!</b> more text</p>"
-
# # The element 'p' has two text elements, "some text " and " more text".
-
# doc.root.get_text.value #-> "some text "
-
1
def get_text path = nil
-
36
rv = nil
-
36
if path
-
element = @elements[ path ]
-
rv = element.get_text unless element.nil?
-
else
-
72
rv = @children.find { |node| node.kind_of? Text }
-
end
-
36
return rv
-
end
-
-
# Sets the first Text child of this object. See text() for a
-
# discussion about Text children.
-
#
-
# If a Text child already exists, the child is replaced by this
-
# content. This means that Text content can be deleted by calling
-
# this method with a nil argument. In this case, the next Text
-
# child becomes the first Text child. In no case is the order of
-
# any siblings disturbed.
-
# text::
-
# If a String, a new Text child is created and added to
-
# this Element as the first Text child. If Text, the text is set
-
# as the first Child element. If nil, then any existing first Text
-
# child is removed.
-
# Returns:: this Element.
-
# doc = Document.new '<a><b/></a>'
-
# doc.root.text = 'Sean' #-> '<a><b/>Sean</a>'
-
# doc.root.text = 'Elliott' #-> '<a><b/>Elliott</a>'
-
# doc.root.add_element 'c' #-> '<a><b/>Elliott<c/></a>'
-
# doc.root.text = 'Russell' #-> '<a><b/>Russell<c/></a>'
-
# doc.root.text = nil #-> '<a><b/><c/></a>'
-
1
def text=( text )
-
if text.kind_of? String
-
text = Text.new( text, whitespace(), nil, raw() )
-
elsif !text.nil? and !text.kind_of? Text
-
text = Text.new( text.to_s, whitespace(), nil, raw() )
-
end
-
old_text = get_text
-
if text.nil?
-
old_text.remove unless old_text.nil?
-
else
-
if old_text.nil?
-
self << text
-
else
-
old_text.replace_with( text )
-
end
-
end
-
return self
-
end
-
-
# A helper method to add a Text child. Actual Text instances can
-
# be added with regular Parent methods, such as add() and <<()
-
# text::
-
# if a String, a new Text instance is created and added
-
# to the parent. If Text, the object is added directly.
-
# Returns:: this Element
-
# e = Element.new('a') #-> <e/>
-
# e.add_text 'foo' #-> <e>foo</e>
-
# e.add_text Text.new(' bar') #-> <e>foo bar</e>
-
# Note that at the end of this example, the branch has <b>3</b> nodes; the 'e'
-
# element and <b>2</b> Text node children.
-
1
def add_text( text )
-
if text.kind_of? String
-
if @children[-1].kind_of? Text
-
@children[-1] << text
-
return
-
end
-
text = Text.new( text, whitespace(), nil, raw() )
-
end
-
self << text unless text.nil?
-
return self
-
end
-
-
1
def node_type
-
50
:element
-
end
-
-
1
def xpath
-
path_elements = []
-
cur = self
-
path_elements << __to_xpath_helper( self )
-
while cur.parent
-
cur = cur.parent
-
path_elements << __to_xpath_helper( cur )
-
end
-
return path_elements.reverse.join( "/" )
-
end
-
-
#################################################
-
# Attributes #
-
#################################################
-
-
1
def attribute( name, namespace=nil )
-
prefix = nil
-
if namespaces.respond_to? :key
-
prefix = namespaces.key(namespace) if namespace
-
else
-
prefix = namespaces.index(namespace) if namespace
-
end
-
prefix = nil if prefix == 'xmlns'
-
-
ret_val =
-
attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" )
-
-
return ret_val unless ret_val.nil?
-
return nil if prefix.nil?
-
-
# now check that prefix'es namespace is not the same as the
-
# default namespace
-
return nil unless ( namespaces[ prefix ] == namespaces[ 'xmlns' ] )
-
-
attributes.get_attribute( name )
-
-
end
-
-
# Evaluates to +true+ if this element has any attributes set, false
-
# otherwise.
-
1
def has_attributes?
-
return !@attributes.empty?
-
end
-
-
# Adds an attribute to this element, overwriting any existing attribute
-
# by the same name.
-
# key::
-
# can be either an Attribute or a String. If an Attribute,
-
# the attribute is added to the list of Element attributes. If String,
-
# the argument is used as the name of the new attribute, and the value
-
# parameter must be supplied.
-
# value::
-
# Required if +key+ is a String, and ignored if the first argument is
-
# an Attribute. This is a String, and is used as the value
-
# of the new Attribute. This should be the unnormalized value of the
-
# attribute (without entities).
-
# Returns:: the Attribute added
-
# e = Element.new 'e'
-
# e.add_attribute( 'a', 'b' ) #-> <e a='b'/>
-
# e.add_attribute( 'x:a', 'c' ) #-> <e a='b' x:a='c'/>
-
# e.add_attribute Attribute.new('b', 'd') #-> <e a='b' x:a='c' b='d'/>
-
1
def add_attribute( key, value=nil )
-
if key.kind_of? Attribute
-
@attributes << key
-
else
-
@attributes[key] = value
-
end
-
end
-
-
# Add multiple attributes to this element.
-
# hash:: is either a hash, or array of arrays
-
# el.add_attributes( {"name1"=>"value1", "name2"=>"value2"} )
-
# el.add_attributes( [ ["name1","value1"], ["name2"=>"value2"] ] )
-
1
def add_attributes hash
-
if hash.kind_of? Hash
-
hash.each_pair {|key, value| @attributes[key] = value }
-
elsif hash.kind_of? Array
-
hash.each { |value| @attributes[ value[0] ] = value[1] }
-
end
-
end
-
-
# Removes an attribute
-
# key::
-
# either an Attribute or a String. In either case, the
-
# attribute is found by matching the attribute name to the argument,
-
# and then removed. If no attribute is found, no action is taken.
-
# Returns::
-
# the attribute removed, or nil if this Element did not contain
-
# a matching attribute
-
# e = Element.new('E')
-
# e.add_attribute( 'name', 'Sean' ) #-> <E name='Sean'/>
-
# r = e.add_attribute( 'sur:name', 'Russell' ) #-> <E name='Sean' sur:name='Russell'/>
-
# e.delete_attribute( 'name' ) #-> <E sur:name='Russell'/>
-
# e.delete_attribute( r ) #-> <E/>
-
1
def delete_attribute(key)
-
attr = @attributes.get_attribute(key)
-
attr.remove unless attr.nil?
-
end
-
-
#################################################
-
# Other Utilities #
-
#################################################
-
-
# Get an array of all CData children.
-
# IMMUTABLE
-
1
def cdatas
-
find_all { |child| child.kind_of? CData }.freeze
-
end
-
-
# Get an array of all Comment children.
-
# IMMUTABLE
-
1
def comments
-
find_all { |child| child.kind_of? Comment }.freeze
-
end
-
-
# Get an array of all Instruction children.
-
# IMMUTABLE
-
1
def instructions
-
find_all { |child| child.kind_of? Instruction }.freeze
-
end
-
-
# Get an array of all Text children.
-
# IMMUTABLE
-
1
def texts
-
146
find_all { |child| child.kind_of? Text }.freeze
-
end
-
-
# == DEPRECATED
-
# See REXML::Formatters
-
#
-
# Writes out this element, and recursively, all children.
-
# output::
-
# output an object which supports '<< string'; this is where the
-
# document will be written.
-
# indent::
-
# An integer. If -1, no indenting will be used; otherwise, the
-
# indentation will be this number of spaces, and children will be
-
# indented an additional amount. Defaults to -1
-
# transitive::
-
# If transitive is true and indent is >= 0, then the output will be
-
# pretty-printed in such a way that the added whitespace does not affect
-
# the parse tree of the document
-
# ie_hack::
-
# Internet Explorer is the worst piece of crap to have ever been
-
# written, with the possible exception of Windows itself. Since IE is
-
# unable to parse proper XML, we have to provide a hack to generate XML
-
# that IE's limited abilities can handle. This hack inserts a space
-
# before the /> on empty tags. Defaults to false
-
#
-
# out = ''
-
# doc.write( out ) #-> doc is written to the string 'out'
-
# doc.write( $stdout ) #-> doc written to the console
-
1
def write(output=$stdout, indent=-1, transitive=false, ie_hack=false)
-
Kernel.warn("#{self.class.name}.write is deprecated. See REXML::Formatters")
-
formatter = if indent > -1
-
if transitive
-
require "rexml/formatters/transitive"
-
REXML::Formatters::Transitive.new( indent, ie_hack )
-
else
-
REXML::Formatters::Pretty.new( indent, ie_hack )
-
end
-
else
-
REXML::Formatters::Default.new( ie_hack )
-
end
-
formatter.write( self, output )
-
end
-
-
-
1
private
-
1
def __to_xpath_helper node
-
rv = node.expanded_name.clone
-
if node.parent
-
results = node.parent.find_all {|n|
-
n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name
-
}
-
if results.length > 1
-
idx = results.index( node )
-
rv << "[#{idx+1}]"
-
end
-
end
-
rv
-
end
-
-
# A private helper method
-
1
def each_with_something( test, max=0, name=nil )
-
num = 0
-
@elements.each( name ){ |child|
-
yield child if test.call(child) and num += 1
-
return if max>0 and num == max
-
}
-
end
-
end
-
-
########################################################################
-
# ELEMENTS #
-
########################################################################
-
-
# A class which provides filtering of children for Elements, and
-
# XPath search support. You are expected to only encounter this class as
-
# the <tt>element.elements</tt> object. Therefore, you are
-
# _not_ expected to instantiate this yourself.
-
1
class Elements
-
1
include Enumerable
-
# Constructor
-
# parent:: the parent Element
-
1
def initialize parent
-
87
@element = parent
-
end
-
-
# Fetches a child element. Filters only Element children, regardless of
-
# the XPath match.
-
# index::
-
# the search parameter. This is either an Integer, which
-
# will be used to find the index'th child Element, or an XPath,
-
# which will be used to search for the Element. <em>Because
-
# of the nature of XPath searches, any element in the connected XML
-
# document can be fetched through any other element.</em> <b>The
-
# Integer index is 1-based, not 0-based.</b> This means that the first
-
# child element is at index 1, not 0, and the +n+th element is at index
-
# +n+, not <tt>n-1</tt>. This is because XPath indexes element children
-
# starting from 1, not 0, and the indexes should be the same.
-
# name::
-
# optional, and only used in the first argument is an
-
# Integer. In that case, the index'th child Element that has the
-
# supplied name will be returned. Note again that the indexes start at 1.
-
# Returns:: the first matching Element, or nil if no child matched
-
# doc = Document.new '<a><b/><c id="1"/><c id="2"/><d/></a>'
-
# doc.root.elements[1] #-> <b/>
-
# doc.root.elements['c'] #-> <c id="1"/>
-
# doc.root.elements[2,'c'] #-> <c id="2"/>
-
1
def []( index, name=nil)
-
48
if index.kind_of? Integer
-
48
raise "index (#{index}) must be >= 1" if index < 1
-
48
name = literalize(name) if name
-
48
num = 0
-
48
@element.find { |child|
-
child.kind_of? Element and
-
56
(name.nil? ? true : child.has_name?( name )) and
-
44
(num += 1) == index
-
}
-
else
-
return XPath::first( @element, index )
-
#{ |element|
-
# return element if element.kind_of? Element
-
#}
-
#return nil
-
end
-
end
-
-
# Sets an element, replacing any previous matching element. If no
-
# existing element is found ,the element is added.
-
# index:: Used to find a matching element to replace. See []().
-
# element::
-
# The element to replace the existing element with
-
# the previous element
-
# Returns:: nil if no previous element was found.
-
#
-
# doc = Document.new '<a/>'
-
# doc.root.elements[10] = Element.new('b') #-> <a><b/></a>
-
# doc.root.elements[1] #-> <b/>
-
# doc.root.elements[1] = Element.new('c') #-> <a><c/></a>
-
# doc.root.elements['c'] = Element.new('d') #-> <a><d/></a>
-
1
def []=( index, element )
-
previous = self[index]
-
if previous.nil?
-
@element.add element
-
else
-
previous.replace_with element
-
end
-
return previous
-
end
-
-
# Returns +true+ if there are no +Element+ children, +false+ otherwise
-
1
def empty?
-
112
@element.find{ |child| child.kind_of? Element}.nil?
-
end
-
-
# Returns the index of the supplied child (starting at 1), or -1 if
-
# the element is not a child
-
# element:: an +Element+ child
-
1
def index element
-
rv = 0
-
found = @element.find do |child|
-
child.kind_of? Element and
-
(rv += 1) and
-
child == element
-
end
-
return rv if found == element
-
return -1
-
end
-
-
# Deletes a child Element
-
# element::
-
# Either an Element, which is removed directly; an
-
# xpath, where the first matching child is removed; or an Integer,
-
# where the n'th Element is removed.
-
# Returns:: the removed child
-
# doc = Document.new '<a><b/><c/><c id="1"/></a>'
-
# b = doc.root.elements[1]
-
# doc.root.elements.delete b #-> <a><c/><c id="1"/></a>
-
# doc.elements.delete("a/c[@id='1']") #-> <a><c/></a>
-
# doc.root.elements.delete 1 #-> <a/>
-
1
def delete element
-
if element.kind_of? Element
-
@element.delete element
-
else
-
el = self[element]
-
el.remove if el
-
end
-
end
-
-
# Removes multiple elements. Filters for Element children, regardless of
-
# XPath matching.
-
# xpath:: all elements matching this String path are removed.
-
# Returns:: an Array of Elements that have been removed
-
# doc = Document.new '<a><c/><c/><c/><c/></a>'
-
# deleted = doc.elements.delete_all 'a/c' #-> [<c/>, <c/>, <c/>, <c/>]
-
1
def delete_all( xpath )
-
rv = []
-
XPath::each( @element, xpath) {|element|
-
rv << element if element.kind_of? Element
-
}
-
rv.each do |element|
-
@element.delete element
-
element.remove
-
end
-
return rv
-
end
-
-
# Adds an element
-
# element::
-
# if supplied, is either an Element, String, or
-
# Source (see Element.initialize). If not supplied or nil, a
-
# new, default Element will be constructed
-
# Returns:: the added Element
-
# a = Element.new('a')
-
# a.elements.add(Element.new('b')) #-> <a><b/></a>
-
# a.elements.add('c') #-> <a><b/><c/></a>
-
1
def add element=nil
-
126
if element.nil?
-
Element.new("", self, @element.context)
-
126
elsif not element.kind_of?(Element)
-
63
Element.new(element, self, @element.context)
-
else
-
63
@element << element
-
63
element.context = @element.context
-
63
element
-
end
-
end
-
-
1
alias :<< :add
-
-
# Iterates through all of the child Elements, optionally filtering
-
# them by a given XPath
-
# xpath::
-
# optional. If supplied, this is a String XPath, and is used to
-
# filter the children, so that only matching children are yielded. Note
-
# that XPaths are automatically filtered for Elements, so that
-
# non-Element children will not be yielded
-
# doc = Document.new '<a><b/><c/><d/>sean<b/><c/><d/></a>'
-
# doc.root.each {|e|p e} #-> Yields b, c, d, b, c, d elements
-
# doc.root.each('b') {|e|p e} #-> Yields b, b elements
-
# doc.root.each('child::node()') {|e|p e}
-
# #-> Yields <b/>, <c/>, <d/>, <b/>, <c/>, <d/>
-
# XPath.each(doc.root, 'child::node()', &block)
-
# #-> Yields <b/>, <c/>, <d/>, sean, <b/>, <c/>, <d/>
-
1
def each( xpath=nil, &block)
-
50
XPath::each( @element, xpath ) {|e| yield e if e.kind_of? Element }
-
end
-
-
1
def collect( xpath=nil, &block )
-
collection = []
-
XPath::each( @element, xpath ) {|e|
-
collection << yield(e) if e.kind_of?(Element)
-
}
-
collection
-
end
-
-
1
def inject( xpath=nil, initial=nil, &block )
-
first = true
-
XPath::each( @element, xpath ) {|e|
-
if (e.kind_of? Element)
-
if (first and initial == nil)
-
initial = e
-
first = false
-
else
-
initial = yield( initial, e ) if e.kind_of? Element
-
end
-
end
-
}
-
initial
-
end
-
-
# Returns the number of +Element+ children of the parent object.
-
# doc = Document.new '<a>sean<b/>elliott<b/>russell<b/></a>'
-
# doc.root.size #-> 6, 3 element and 3 text nodes
-
# doc.root.elements.size #-> 3
-
1
def size
-
56
count = 0
-
128
@element.each {|child| count+=1 if child.kind_of? Element }
-
56
count
-
end
-
-
# Returns an Array of Element children. An XPath may be supplied to
-
# filter the children. Only Element children are returned, even if the
-
# supplied XPath matches non-Element children.
-
# doc = Document.new '<a>sean<b/>elliott<c/></a>'
-
# doc.root.elements.to_a #-> [ <b/>, <c/> ]
-
# doc.root.elements.to_a("child::node()") #-> [ <b/>, <c/> ]
-
# XPath.match(doc.root, "child::node()") #-> [ sean, <b/>, elliott, <c/> ]
-
1
def to_a( xpath=nil )
-
rv = XPath.match( @element, xpath )
-
return rv.find_all{|e| e.kind_of? Element} if xpath
-
rv
-
end
-
-
1
private
-
# Private helper class. Removes quotes from quoted strings
-
1
def literalize name
-
name = name[1..-2] if name[0] == ?' or name[0] == ?" #'
-
name
-
end
-
end
-
-
########################################################################
-
# ATTRIBUTES #
-
########################################################################
-
-
# A class that defines the set of Attributes of an Element and provides
-
# operations for accessing elements in that set.
-
1
class Attributes < Hash
-
# Constructor
-
# element:: the Element of which this is an Attribute
-
1
def initialize element
-
87
@element = element
-
end
-
-
# Fetches an attribute value. If you want to get the Attribute itself,
-
# use get_attribute()
-
# name:: an XPath attribute name. Namespaces are relevant here.
-
# Returns::
-
# the String value of the matching attribute, or +nil+ if no
-
# matching attribute was found. This is the unnormalized value
-
# (with entities expanded).
-
#
-
# doc = Document.new "<a foo:att='1' bar:att='2' att='<'/>"
-
# doc.root.attributes['att'] #-> '<'
-
# doc.root.attributes['bar:att'] #-> '2'
-
1
def [](name)
-
attr = get_attribute(name)
-
return attr.value unless attr.nil?
-
return nil
-
end
-
-
1
def to_a
-
values.flatten
-
end
-
-
# Returns the number of attributes the owning Element contains.
-
# doc = Document "<a x='1' y='2' foo:x='3'/>"
-
# doc.root.attributes.length #-> 3
-
1
def length
-
c = 0
-
each_attribute { c+=1 }
-
c
-
end
-
1
alias :size :length
-
-
# Iterates over the attributes of an Element. Yields actual Attribute
-
# nodes, not String values.
-
#
-
# doc = Document.new '<a x="1" y="2"/>'
-
# doc.root.attributes.each_attribute {|attr|
-
# p attr.expanded_name+" => "+attr.value
-
# }
-
1
def each_attribute # :yields: attribute
-
53
each_value do |val|
-
33
if val.kind_of? Attribute
-
33
yield val
-
else
-
val.each_value { |atr| yield atr }
-
end
-
end
-
end
-
-
# Iterates over each attribute of an Element, yielding the expanded name
-
# and value as a pair of Strings.
-
#
-
# doc = Document.new '<a x="1" y="2"/>'
-
# doc.root.attributes.each {|name, value| p name+" => "+value }
-
1
def each
-
53
each_attribute do |attr|
-
33
yield [attr.expanded_name, attr.value]
-
end
-
end
-
-
# Fetches an attribute
-
# name::
-
# the name by which to search for the attribute. Can be a
-
# <tt>prefix:name</tt> namespace name.
-
# Returns:: The first matching attribute, or nil if there was none. This
-
# value is an Attribute node, not the String value of the attribute.
-
# doc = Document.new '<a x:foo="1" foo="2" bar="3"/>'
-
# doc.root.attributes.get_attribute("foo").value #-> "2"
-
# doc.root.attributes.get_attribute("x:foo").value #-> "1"
-
1
def get_attribute( name )
-
attr = fetch( name, nil )
-
if attr.nil?
-
return nil if name.nil?
-
# Look for prefix
-
name =~ Namespace::NAMESPLIT
-
prefix, n = $1, $2
-
if prefix
-
attr = fetch( n, nil )
-
# check prefix
-
if attr == nil
-
elsif attr.kind_of? Attribute
-
return attr if prefix == attr.prefix
-
else
-
attr = attr[ prefix ]
-
return attr
-
end
-
end
-
element_document = @element.document
-
if element_document and element_document.doctype
-
expn = @element.expanded_name
-
expn = element_document.doctype.name if expn.size == 0
-
attr_val = element_document.doctype.attribute_of(expn, name)
-
return Attribute.new( name, attr_val ) if attr_val
-
end
-
return nil
-
end
-
if attr.kind_of? Hash
-
attr = attr[ @element.prefix ]
-
end
-
return attr
-
end
-
-
# Sets an attribute, overwriting any existing attribute value by the
-
# same name. Namespace is significant.
-
# name:: the name of the attribute
-
# value::
-
# (optional) If supplied, the value of the attribute. If
-
# nil, any existing matching attribute is deleted.
-
# Returns::
-
# Owning element
-
# doc = Document.new "<a x:foo='1' foo='3'/>"
-
# doc.root.attributes['y:foo'] = '2'
-
# doc.root.attributes['foo'] = '4'
-
# doc.root.attributes['x:foo'] = nil
-
1
def []=( name, value )
-
39
if value.nil? # Delete the named attribute
-
attr = get_attribute(name)
-
delete attr
-
return
-
end
-
-
39
unless value.kind_of? Attribute
-
if @element.document and @element.document.doctype
-
value = Text::normalize( value, @element.document.doctype )
-
else
-
value = Text::normalize( value, nil )
-
end
-
value = Attribute.new(name, value)
-
end
-
39
value.element = @element
-
39
old_attr = fetch(value.name, nil)
-
39
if old_attr.nil?
-
39
store(value.name, value)
-
elsif old_attr.kind_of? Hash
-
old_attr[value.prefix] = value
-
elsif old_attr.prefix != value.prefix
-
# Check for conflicting namespaces
-
raise ParseException.new(
-
"Namespace conflict in adding attribute \"#{value.name}\": "+
-
"Prefix \"#{old_attr.prefix}\" = "+
-
"\"#{@element.namespace(old_attr.prefix)}\" and prefix "+
-
"\"#{value.prefix}\" = \"#{@element.namespace(value.prefix)}\"") if
-
value.prefix != "xmlns" and old_attr.prefix != "xmlns" and
-
@element.namespace( old_attr.prefix ) ==
-
@element.namespace( value.prefix )
-
store value.name, { old_attr.prefix => old_attr,
-
value.prefix => value }
-
else
-
store value.name, value
-
end
-
39
return @element
-
end
-
-
# Returns an array of Strings containing all of the prefixes declared
-
# by this set of # attributes. The array does not include the default
-
# namespace declaration, if one exists.
-
# doc = Document.new("<a xmlns='foo' xmlns:x='bar' xmlns:y='twee' "+
-
# "z='glorp' p:k='gru'/>")
-
# prefixes = doc.root.attributes.prefixes #-> ['x', 'y']
-
1
def prefixes
-
ns = []
-
each_attribute do |attribute|
-
ns << attribute.name if attribute.prefix == 'xmlns'
-
end
-
if @element.document and @element.document.doctype
-
expn = @element.expanded_name
-
expn = @element.document.doctype.name if expn.size == 0
-
@element.document.doctype.attributes_of(expn).each {
-
|attribute|
-
ns << attribute.name if attribute.prefix == 'xmlns'
-
}
-
end
-
ns
-
end
-
-
1
def namespaces
-
namespaces = {}
-
each_attribute do |attribute|
-
namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
-
end
-
if @element.document and @element.document.doctype
-
expn = @element.expanded_name
-
expn = @element.document.doctype.name if expn.size == 0
-
@element.document.doctype.attributes_of(expn).each {
-
|attribute|
-
namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
-
}
-
end
-
namespaces
-
end
-
-
# Removes an attribute
-
# attribute::
-
# either a String, which is the name of the attribute to remove --
-
# namespaces are significant here -- or the attribute to remove.
-
# Returns:: the owning element
-
# doc = Document.new "<a y:foo='0' x:foo='1' foo='3' z:foo='4'/>"
-
# doc.root.attributes.delete 'foo' #-> <a y:foo='0' x:foo='1' z:foo='4'/>"
-
# doc.root.attributes.delete 'x:foo' #-> <a y:foo='0' z:foo='4'/>"
-
# attr = doc.root.attributes.get_attribute('y:foo')
-
# doc.root.attributes.delete attr #-> <a z:foo='4'/>"
-
1
def delete( attribute )
-
name = nil
-
prefix = nil
-
if attribute.kind_of? Attribute
-
name = attribute.name
-
prefix = attribute.prefix
-
else
-
attribute =~ Namespace::NAMESPLIT
-
prefix, name = $1, $2
-
prefix = '' unless prefix
-
end
-
old = fetch(name, nil)
-
attr = nil
-
if old.kind_of? Hash # the supplied attribute is one of many
-
attr = old.delete(prefix)
-
if old.size == 1
-
repl = nil
-
old.each_value{|v| repl = v}
-
store name, repl
-
end
-
elsif old.nil?
-
return @element
-
else # the supplied attribute is a top-level one
-
attr = old
-
super(name)
-
end
-
@element
-
end
-
-
# Adds an attribute, overriding any existing attribute by the
-
# same name. Namespaces are significant.
-
# attribute:: An Attribute
-
1
def add( attribute )
-
self[attribute.name] = attribute
-
end
-
-
1
alias :<< :add
-
-
# Deletes all attributes matching a name. Namespaces are significant.
-
# name::
-
# A String; all attributes that match this path will be removed
-
# Returns:: an Array of the Attributes that were removed
-
1
def delete_all( name )
-
rv = []
-
each_attribute { |attribute|
-
rv << attribute if attribute.expanded_name == name
-
}
-
rv.each{ |attr| attr.remove }
-
return rv
-
end
-
-
# The +get_attribute_ns+ method retrieves a method by its namespace
-
# and name. Thus it is possible to reliably identify an attribute
-
# even if an XML processor has changed the prefix.
-
#
-
# Method contributed by Henrik Martensson
-
1
def get_attribute_ns(namespace, name)
-
result = nil
-
each_attribute() { |attribute|
-
if name == attribute.name &&
-
namespace == attribute.namespace() &&
-
( !namespace.empty? || !attribute.fully_expanded_name.index(':') )
-
# foo will match xmlns:foo, but only if foo isn't also an attribute
-
result = attribute if !result or !namespace.empty? or
-
!attribute.fully_expanded_name.index(':')
-
end
-
}
-
result
-
end
-
end
-
end
-
1
module REXML
-
1
module Encoding
-
# ID ---> Encoding name
-
1
attr_reader :encoding
-
1
def encoding=(encoding)
-
25
encoding = encoding.name if encoding.is_a?(Encoding)
-
25
if encoding.is_a?(String)
-
25
original_encoding = encoding
-
25
encoding = find_encoding(encoding)
-
25
unless encoding
-
raise ArgumentError, "Bad encoding name #{original_encoding}"
-
end
-
end
-
25
return false if defined?(@encoding) and encoding == @encoding
-
25
if encoding
-
25
@encoding = encoding.upcase
-
else
-
@encoding = 'UTF-8'
-
end
-
25
true
-
end
-
-
1
def check_encoding(xml)
-
# We have to recognize UTF-16BE, UTF-16LE, and UTF-8
-
24
if xml[0, 2] == "\xfe\xff"
-
xml[0, 2] = ""
-
return 'UTF-16BE'
-
elsif xml[0, 2] == "\xff\xfe"
-
xml[0, 2] = ""
-
return 'UTF-16LE'
-
end
-
24
xml =~ /^\s*<\?xml\s+version\s*=\s*(['"]).*?\1\s+encoding\s*=\s*(["'])(.*?)\2/m
-
24
return $3 ? $3.upcase : 'UTF-8'
-
end
-
-
1
def encode(string)
-
24
string.encode(@encoding)
-
end
-
-
1
def decode(string)
-
string.encode(::Encoding::UTF_8, @encoding)
-
end
-
-
1
private
-
1
def find_encoding(name)
-
25
case name
-
when /\Ashift-jis\z/i
-
return "SHIFT_JIS"
-
when /\ACP-(\d+)\z/
-
name = "CP#{$1}"
-
when /\AUTF-8\z/i
-
25
return name
-
end
-
begin
-
::Encoding::Converter.search_convpath(name, 'UTF-8')
-
rescue ::Encoding::ConverterNotFoundError
-
return nil
-
end
-
name
-
end
-
end
-
end
-
1
require 'rexml/child'
-
1
require 'rexml/source'
-
1
require 'rexml/xmltokens'
-
-
1
module REXML
-
# God, I hate DTDs. I really do. Why this idiot standard still
-
# plagues us is beyond me.
-
1
class Entity < Child
-
1
include XMLTokens
-
1
PUBIDCHAR = "\x20\x0D\x0Aa-zA-Z0-9\\-()+,./:=?;!*@$_%#"
-
1
SYSTEMLITERAL = %Q{((?:"[^"]*")|(?:'[^']*'))}
-
1
PUBIDLITERAL = %Q{("[#{PUBIDCHAR}']*"|'[#{PUBIDCHAR}]*')}
-
1
EXTERNALID = "(?:(?:(SYSTEM)\\s+#{SYSTEMLITERAL})|(?:(PUBLIC)\\s+#{PUBIDLITERAL}\\s+#{SYSTEMLITERAL}))"
-
1
NDATADECL = "\\s+NDATA\\s+#{NAME}"
-
1
PEREFERENCE = "%#{NAME};"
-
1
ENTITYVALUE = %Q{((?:"(?:[^%&"]|#{PEREFERENCE}|#{REFERENCE})*")|(?:'([^%&']|#{PEREFERENCE}|#{REFERENCE})*'))}
-
1
PEDEF = "(?:#{ENTITYVALUE}|#{EXTERNALID})"
-
1
ENTITYDEF = "(?:#{ENTITYVALUE}|(?:#{EXTERNALID}(#{NDATADECL})?))"
-
1
PEDECL = "<!ENTITY\\s+(%)\\s+#{NAME}\\s+#{PEDEF}\\s*>"
-
1
GEDECL = "<!ENTITY\\s+#{NAME}\\s+#{ENTITYDEF}\\s*>"
-
1
ENTITYDECL = /\s*(?:#{GEDECL})|(?:#{PEDECL})/um
-
-
1
attr_reader :name, :external, :ref, :ndata, :pubid
-
-
# Create a new entity. Simple entities can be constructed by passing a
-
# name, value to the constructor; this creates a generic, plain entity
-
# reference. For anything more complicated, you have to pass a Source to
-
# the constructor with the entity definition, or use the accessor methods.
-
# +WARNING+: There is no validation of entity state except when the entity
-
# is read from a stream. If you start poking around with the accessors,
-
# you can easily create a non-conformant Entity. The best thing to do is
-
# dump the stupid DTDs and use XMLSchema instead.
-
#
-
# e = Entity.new( 'amp', '&' )
-
1
def initialize stream, value=nil, parent=nil, reference=false
-
5
super(parent)
-
5
@ndata = @pubid = @value = @external = nil
-
5
if stream.kind_of? Array
-
@name = stream[1]
-
if stream[-1] == '%'
-
@reference = true
-
stream.pop
-
else
-
@reference = false
-
end
-
if stream[2] =~ /SYSTEM|PUBLIC/
-
@external = stream[2]
-
if @external == 'SYSTEM'
-
@ref = stream[3]
-
@ndata = stream[4] if stream.size == 5
-
else
-
@pubid = stream[3]
-
@ref = stream[4]
-
end
-
else
-
@value = stream[2]
-
end
-
else
-
5
@reference = reference
-
5
@external = nil
-
5
@name = stream
-
5
@value = value
-
end
-
end
-
-
# Evaluates whether the given string matchs an entity definition,
-
# returning true if so, and false otherwise.
-
1
def Entity::matches? string
-
(ENTITYDECL =~ string) == 0
-
end
-
-
# Evaluates to the unnormalized value of this entity; that is, replacing
-
# all entities -- both %ent; and &ent; entities. This differs from
-
# +value()+ in that +value+ only replaces %ent; entities.
-
1
def unnormalized
-
document.record_entity_expansion unless document.nil?
-
v = value()
-
return nil if v.nil?
-
@unnormalized = Text::unnormalize(v, parent)
-
@unnormalized
-
end
-
-
#once :unnormalized
-
-
# Returns the value of this entity unprocessed -- raw. This is the
-
# normalized value; that is, with all %ent; and &ent; entities intact
-
1
def normalized
-
@value
-
end
-
-
# Write out a fully formed, correct entity definition (assuming the Entity
-
# object itself is valid.)
-
#
-
# out::
-
# An object implementing <TT><<<TT> to which the entity will be
-
# output
-
# indent::
-
# *DEPRECATED* and ignored
-
1
def write out, indent=-1
-
out << '<!ENTITY '
-
out << '% ' if @reference
-
out << @name
-
out << ' '
-
if @external
-
out << @external << ' '
-
if @pubid
-
q = @pubid.include?('"')?"'":'"'
-
out << q << @pubid << q << ' '
-
end
-
q = @ref.include?('"')?"'":'"'
-
out << q << @ref << q
-
out << ' NDATA ' << @ndata if @ndata
-
else
-
q = @value.include?('"')?"'":'"'
-
out << q << @value << q
-
end
-
out << '>'
-
end
-
-
# Returns this entity as a string. See write().
-
1
def to_s
-
rv = ''
-
write rv
-
rv
-
end
-
-
1
PEREFERENCE_RE = /#{PEREFERENCE}/um
-
# Returns the value of this entity. At the moment, only internal entities
-
# are processed. If the value contains internal references (IE,
-
# %blah;), those are replaced with their values. IE, if the doctype
-
# contains:
-
# <!ENTITY % foo "bar">
-
# <!ENTITY yada "nanoo %foo; nanoo>
-
# then:
-
# doctype.entity('yada').value #-> "nanoo bar nanoo"
-
1
def value
-
5
if @value
-
5
matches = @value.scan(PEREFERENCE_RE)
-
5
rv = @value.clone
-
5
if @parent
-
matches.each do |entity_reference|
-
entity_value = @parent.entity( entity_reference[0] )
-
rv.gsub!( /%#{entity_reference.join};/um, entity_value )
-
end
-
end
-
5
return rv
-
end
-
nil
-
end
-
end
-
-
# This is a set of entity constants -- the ones defined in the XML
-
# specification. These are +gt+, +lt+, +amp+, +quot+ and +apos+.
-
1
module EntityConst
-
# +>+
-
1
GT = Entity.new( 'gt', '>' )
-
# +<+
-
1
LT = Entity.new( 'lt', '<' )
-
# +&+
-
1
AMP = Entity.new( 'amp', '&' )
-
# +"+
-
1
QUOT = Entity.new( 'quot', '"' )
-
# +'+
-
1
APOS = Entity.new( 'apos', "'" )
-
end
-
end
-
1
module REXML
-
1
module Formatters
-
1
class Default
-
# Prints out the XML document with no formatting -- except if id_hack is
-
# set.
-
#
-
# ie_hack::
-
# If set to true, then inserts whitespace before the close of an empty
-
# tag, so that IE's bad XML parser doesn't choke.
-
1
def initialize( ie_hack=false )
-
@ie_hack = ie_hack
-
end
-
-
# Writes the node to some output.
-
#
-
# node::
-
# The node to write
-
# output::
-
# A class implementing <TT><<</TT>. Pass in an Output object to
-
# change the output encoding.
-
1
def write( node, output )
-
case node
-
-
when Document
-
if node.xml_decl.encoding != 'UTF-8' && !output.kind_of?(Output)
-
output = Output.new( output, node.xml_decl.encoding )
-
end
-
write_document( node, output )
-
-
when Element
-
write_element( node, output )
-
-
when Declaration, ElementDecl, NotationDecl, ExternalEntity, Entity,
-
Attribute, AttlistDecl
-
node.write( output,-1 )
-
-
when Instruction
-
write_instruction( node, output )
-
-
when DocType, XMLDecl
-
node.write( output )
-
-
when Comment
-
write_comment( node, output )
-
-
when CData
-
write_cdata( node, output )
-
-
when Text
-
write_text( node, output )
-
-
else
-
raise Exception.new("XML FORMATTING ERROR")
-
-
end
-
end
-
-
1
protected
-
1
def write_document( node, output )
-
node.children.each { |child| write( child, output ) }
-
end
-
-
1
def write_element( node, output )
-
output << "<#{node.expanded_name}"
-
-
node.attributes.to_a.map { |a|
-
Hash === a ? a.values : a
-
}.flatten.sort_by {|attr| attr.name}.each do |attr|
-
output << " "
-
attr.write( output )
-
end unless node.attributes.empty?
-
-
if node.children.empty?
-
output << " " if @ie_hack
-
output << "/"
-
else
-
output << ">"
-
node.children.each { |child|
-
write( child, output )
-
}
-
output << "</#{node.expanded_name}"
-
end
-
output << ">"
-
end
-
-
1
def write_text( node, output )
-
output << node.to_s()
-
end
-
-
1
def write_comment( node, output )
-
output << Comment::START
-
output << node.to_s
-
output << Comment::STOP
-
end
-
-
1
def write_cdata( node, output )
-
output << CData::START
-
output << node.to_s
-
output << CData::STOP
-
end
-
-
1
def write_instruction( node, output )
-
output << Instruction::START.sub(/\\/u, '')
-
output << node.target
-
output << ' '
-
output << node.content
-
output << Instruction::STOP.sub(/\\/u, '')
-
end
-
end
-
end
-
end
-
1
require 'rexml/formatters/default'
-
-
1
module REXML
-
1
module Formatters
-
# Pretty-prints an XML document. This destroys whitespace in text nodes
-
# and will insert carriage returns and indentations.
-
#
-
# TODO: Add an option to print attributes on new lines
-
1
class Pretty < Default
-
-
# If compact is set to true, then the formatter will attempt to use as
-
# little space as possible
-
1
attr_accessor :compact
-
# The width of a page. Used for formatting text
-
1
attr_accessor :width
-
-
# Create a new pretty printer.
-
#
-
# output::
-
# An object implementing '<<(String)', to which the output will be written.
-
# indentation::
-
# An integer greater than 0. The indentation of each level will be
-
# this number of spaces. If this is < 1, the behavior of this object
-
# is undefined. Defaults to 2.
-
# ie_hack::
-
# If true, the printer will insert whitespace before closing empty
-
# tags, thereby allowing Internet Explorer's feeble XML parser to
-
# function. Defaults to false.
-
1
def initialize( indentation=2, ie_hack=false )
-
@indentation = indentation
-
@level = 0
-
@ie_hack = ie_hack
-
@width = 80
-
@compact = false
-
end
-
-
1
protected
-
1
def write_element(node, output)
-
output << ' '*@level
-
output << "<#{node.expanded_name}"
-
-
node.attributes.each_attribute do |attr|
-
output << " "
-
attr.write( output )
-
end unless node.attributes.empty?
-
-
if node.children.empty?
-
if @ie_hack
-
output << " "
-
end
-
output << "/"
-
else
-
output << ">"
-
# If compact and all children are text, and if the formatted output
-
# is less than the specified width, then try to print everything on
-
# one line
-
skip = false
-
if compact
-
if node.children.inject(true) {|s,c| s & c.kind_of?(Text)}
-
string = ""
-
old_level = @level
-
@level = 0
-
node.children.each { |child| write( child, string ) }
-
@level = old_level
-
if string.length < @width
-
output << string
-
skip = true
-
end
-
end
-
end
-
unless skip
-
output << "\n"
-
@level += @indentation
-
node.children.each { |child|
-
next if child.kind_of?(Text) and child.to_s.strip.length == 0
-
write( child, output )
-
output << "\n"
-
}
-
@level -= @indentation
-
output << ' '*@level
-
end
-
output << "</#{node.expanded_name}"
-
end
-
output << ">"
-
end
-
-
1
def write_text( node, output )
-
s = node.to_s()
-
s.gsub!(/\s/,' ')
-
s.squeeze!(" ")
-
s = wrap(s, @width - @level)
-
s = indent_text(s, @level, " ", true)
-
output << (' '*@level + s)
-
end
-
-
1
def write_comment( node, output)
-
output << ' ' * @level
-
super
-
end
-
-
1
def write_cdata( node, output)
-
output << ' ' * @level
-
super
-
end
-
-
1
def write_document( node, output )
-
# Ok, this is a bit odd. All XML documents have an XML declaration,
-
# but it may not write itself if the user didn't specifically add it,
-
# either through the API or in the input document. If it doesn't write
-
# itself, then we don't need a carriage return... which makes this
-
# logic more complex.
-
node.children.each { |child|
-
next if child == node.children[-1] and child.instance_of?(Text)
-
unless child == node.children[0] or child.instance_of?(Text) or
-
(child == node.children[1] and !node.children[0].writethis)
-
output << "\n"
-
end
-
write( child, output )
-
}
-
end
-
-
1
private
-
1
def indent_text(string, level=1, style="\t", indentfirstline=true)
-
return string if level < 0
-
string.gsub(/\n/, "\n#{style*level}")
-
end
-
-
1
def wrap(string, width)
-
parts = []
-
while string.length > width and place = string.rindex(' ', width)
-
parts << string[0...place]
-
string = string[place+1..-1]
-
end
-
parts << string
-
parts.join("\n")
-
end
-
-
end
-
end
-
end
-
-
1
module REXML
-
# If you add a method, keep in mind two things:
-
# (1) the first argument will always be a list of nodes from which to
-
# filter. In the case of context methods (such as position), the function
-
# should return an array with a value for each child in the array.
-
# (2) all method calls from XML will have "-" replaced with "_".
-
# Therefore, in XML, "local-name()" is identical (and actually becomes)
-
# "local_name()"
-
1
module Functions
-
1
@@context = nil
-
1
@@namespace_context = {}
-
1
@@variables = {}
-
-
18
def Functions::namespace_context=(x) ; @@namespace_context=x ; end
-
18
def Functions::variables=(x) ; @@variables=x ; end
-
1
def Functions::namespace_context ; @@namespace_context ; end
-
1
def Functions::variables ; @@variables ; end
-
-
1
def Functions::context=(value); @@context = value; end
-
-
1
def Functions::text( )
-
if @@context[:node].node_type == :element
-
return @@context[:node].find_all{|n| n.node_type == :text}.collect{|n| n.value}
-
elsif @@context[:node].node_type == :text
-
return @@context[:node].value
-
else
-
return false
-
end
-
end
-
-
# Returns the last node of the given list of nodes.
-
1
def Functions::last( )
-
@@context[:size]
-
end
-
-
1
def Functions::position( )
-
@@context[:index]
-
end
-
-
# Returns the size of the given list of nodes.
-
1
def Functions::count( node_set )
-
node_set.size
-
end
-
-
# Since REXML is non-validating, this method is not implemented as it
-
# requires a DTD
-
1
def Functions::id( object )
-
end
-
-
# UNTESTED
-
1
def Functions::local_name( node_set=nil )
-
get_namespace( node_set ) do |node|
-
return node.local_name
-
end
-
end
-
-
1
def Functions::namespace_uri( node_set=nil )
-
get_namespace( node_set ) {|node| node.namespace}
-
end
-
-
1
def Functions::name( node_set=nil )
-
get_namespace( node_set ) do |node|
-
node.expanded_name
-
end
-
end
-
-
# Helper method.
-
1
def Functions::get_namespace( node_set = nil )
-
if node_set == nil
-
yield @@context[:node] if defined? @@context[:node].namespace
-
else
-
if node_set.respond_to? :each
-
node_set.each { |node| yield node if defined? node.namespace }
-
elsif node_set.respond_to? :namespace
-
yield node_set
-
end
-
end
-
end
-
-
# A node-set is converted to a string by returning the string-value of the
-
# node in the node-set that is first in document order. If the node-set is
-
# empty, an empty string is returned.
-
#
-
# A number is converted to a string as follows
-
#
-
# NaN is converted to the string NaN
-
#
-
# positive zero is converted to the string 0
-
#
-
# negative zero is converted to the string 0
-
#
-
# positive infinity is converted to the string Infinity
-
#
-
# negative infinity is converted to the string -Infinity
-
#
-
# if the number is an integer, the number is represented in decimal form
-
# as a Number with no decimal point and no leading zeros, preceded by a
-
# minus sign (-) if the number is negative
-
#
-
# otherwise, the number is represented in decimal form as a Number
-
# including a decimal point with at least one digit before the decimal
-
# point and at least one digit after the decimal point, preceded by a
-
# minus sign (-) if the number is negative; there must be no leading zeros
-
# before the decimal point apart possibly from the one required digit
-
# immediately before the decimal point; beyond the one required digit
-
# after the decimal point there must be as many, but only as many, more
-
# digits as are needed to uniquely distinguish the number from all other
-
# IEEE 754 numeric values.
-
#
-
# The boolean false value is converted to the string false. The boolean
-
# true value is converted to the string true.
-
#
-
# An object of a type other than the four basic types is converted to a
-
# string in a way that is dependent on that type.
-
1
def Functions::string( object=nil )
-
#object = @context unless object
-
if object.instance_of? Array
-
string( object[0] )
-
elsif defined? object.node_type
-
if object.node_type == :attribute
-
object.value
-
elsif object.node_type == :element || object.node_type == :document
-
string_value(object)
-
else
-
object.to_s
-
end
-
elsif object.nil?
-
return ""
-
else
-
object.to_s
-
end
-
end
-
-
# A node-set is converted to a string by
-
# returning the concatenation of the string-value
-
# of each of the children of the node in the
-
# node-set that is first in document order.
-
# If the node-set is empty, an empty string is returned.
-
1
def Functions::string_value( o )
-
rv = ""
-
o.children.each { |e|
-
if e.node_type == :text
-
rv << e.to_s
-
elsif e.node_type == :element
-
rv << string_value( e )
-
end
-
}
-
rv
-
end
-
-
# UNTESTED
-
1
def Functions::concat( *objects )
-
objects.join
-
end
-
-
# Fixed by Mike Stok
-
1
def Functions::starts_with( string, test )
-
string(string).index(string(test)) == 0
-
end
-
-
# Fixed by Mike Stok
-
1
def Functions::contains( string, test )
-
string(string).include?(string(test))
-
end
-
-
# Kouhei fixed this
-
1
def Functions::substring_before( string, test )
-
ruby_string = string(string)
-
ruby_index = ruby_string.index(string(test))
-
if ruby_index.nil?
-
""
-
else
-
ruby_string[ 0...ruby_index ]
-
end
-
end
-
-
# Kouhei fixed this too
-
1
def Functions::substring_after( string, test )
-
ruby_string = string(string)
-
return $1 if ruby_string =~ /#{test}(.*)/
-
""
-
end
-
-
# Take equal portions of Mike Stok and Sean Russell; mix
-
# vigorously, and pour into a tall, chilled glass. Serves 10,000.
-
1
def Functions::substring( string, start, length=nil )
-
ruby_string = string(string)
-
ruby_length = if length.nil?
-
ruby_string.length.to_f
-
else
-
number(length)
-
end
-
ruby_start = number(start)
-
-
# Handle the special cases
-
return '' if (
-
ruby_length.nan? or
-
ruby_start.nan? or
-
ruby_start.infinite?
-
)
-
-
infinite_length = ruby_length.infinite? == 1
-
ruby_length = ruby_string.length if infinite_length
-
-
# Now, get the bounds. The XPath bounds are 1..length; the ruby bounds
-
# are 0..length. Therefore, we have to offset the bounds by one.
-
ruby_start = ruby_start.round - 1
-
ruby_length = ruby_length.round
-
-
if ruby_start < 0
-
ruby_length += ruby_start unless infinite_length
-
ruby_start = 0
-
end
-
return '' if ruby_length <= 0
-
ruby_string[ruby_start,ruby_length]
-
end
-
-
# UNTESTED
-
1
def Functions::string_length( string )
-
string(string).length
-
end
-
-
# UNTESTED
-
1
def Functions::normalize_space( string=nil )
-
string = string(@@context[:node]) if string.nil?
-
if string.kind_of? Array
-
string.collect{|x| string.to_s.strip.gsub(/\s+/um, ' ') if string}
-
else
-
string.to_s.strip.gsub(/\s+/um, ' ')
-
end
-
end
-
-
# This is entirely Mike Stok's beast
-
1
def Functions::translate( string, tr1, tr2 )
-
from = string(tr1)
-
to = string(tr2)
-
-
# the map is our translation table.
-
#
-
# if a character occurs more than once in the
-
# from string then we ignore the second &
-
# subsequent mappings
-
#
-
# if a character maps to nil then we delete it
-
# in the output. This happens if the from
-
# string is longer than the to string
-
#
-
# there's nothing about - or ^ being special in
-
# http://www.w3.org/TR/xpath#function-translate
-
# so we don't build ranges or negated classes
-
-
map = Hash.new
-
0.upto(from.length - 1) { |pos|
-
from_char = from[pos]
-
unless map.has_key? from_char
-
map[from_char] =
-
if pos < to.length
-
to[pos]
-
else
-
nil
-
end
-
end
-
}
-
-
if ''.respond_to? :chars
-
string(string).chars.collect { |c|
-
if map.has_key? c then map[c] else c end
-
}.compact.join
-
else
-
string(string).unpack('U*').collect { |c|
-
if map.has_key? c then map[c] else c end
-
}.compact.pack('U*')
-
end
-
end
-
-
# UNTESTED
-
1
def Functions::boolean( object=nil )
-
if object.kind_of? String
-
if object =~ /\d+/u
-
return object.to_f != 0
-
else
-
return object.size > 0
-
end
-
elsif object.kind_of? Array
-
object = object.find{|x| x and true}
-
end
-
return object ? true : false
-
end
-
-
# UNTESTED
-
1
def Functions::not( object )
-
not boolean( object )
-
end
-
-
# UNTESTED
-
1
def Functions::true( )
-
true
-
end
-
-
# UNTESTED
-
1
def Functions::false( )
-
false
-
end
-
-
# UNTESTED
-
1
def Functions::lang( language )
-
lang = false
-
node = @@context[:node]
-
attr = nil
-
until node.nil?
-
if node.node_type == :element
-
attr = node.attributes["xml:lang"]
-
unless attr.nil?
-
lang = compare_language(string(language), attr)
-
break
-
else
-
end
-
end
-
node = node.parent
-
end
-
lang
-
end
-
-
1
def Functions::compare_language lang1, lang2
-
lang2.downcase.index(lang1.downcase) == 0
-
end
-
-
# a string that consists of optional whitespace followed by an optional
-
# minus sign followed by a Number followed by whitespace is converted to
-
# the IEEE 754 number that is nearest (according to the IEEE 754
-
# round-to-nearest rule) to the mathematical value represented by the
-
# string; any other string is converted to NaN
-
#
-
# boolean true is converted to 1; boolean false is converted to 0
-
#
-
# a node-set is first converted to a string as if by a call to the string
-
# function and then converted in the same way as a string argument
-
#
-
# an object of a type other than the four basic types is converted to a
-
# number in a way that is dependent on that type
-
1
def Functions::number( object=nil )
-
object = @@context[:node] unless object
-
case object
-
when true
-
Float(1)
-
when false
-
Float(0)
-
when Array
-
number(string( object ))
-
when Numeric
-
object.to_f
-
else
-
str = string( object )
-
# If XPath ever gets scientific notation...
-
#if str =~ /^\s*-?(\d*\.?\d+|\d+\.)([Ee]\d*)?\s*$/
-
if str =~ /^\s*-?(\d*\.?\d+|\d+\.)\s*$/
-
str.to_f
-
else
-
(0.0 / 0.0)
-
end
-
end
-
end
-
-
1
def Functions::sum( nodes )
-
nodes = [nodes] unless nodes.kind_of? Array
-
nodes.inject(0) { |r,n| r += number(string(n)) }
-
end
-
-
1
def Functions::floor( number )
-
number(number).floor
-
end
-
-
1
def Functions::ceiling( number )
-
number(number).ceil
-
end
-
-
1
def Functions::round( number )
-
begin
-
number(number).round
-
rescue FloatDomainError
-
number(number)
-
end
-
end
-
-
1
def Functions::processing_instruction( node )
-
node.node_type == :processing_instruction
-
end
-
-
1
def Functions::method_missing( id )
-
puts "METHOD MISSING #{id.id2name}"
-
XPath.match( @@context[:node], id.id2name )
-
end
-
end
-
end
-
1
require "rexml/child"
-
1
require "rexml/source"
-
-
1
module REXML
-
# Represents an XML Instruction; IE, <? ... ?>
-
# TODO: Add parent arg (3rd arg) to constructor
-
1
class Instruction < Child
-
1
START = '<\?'
-
1
STOP = '\?>'
-
-
# target is the "name" of the Instruction; IE, the "tag" in <?tag ...?>
-
# content is everything else.
-
1
attr_accessor :target, :content
-
-
# Constructs a new Instruction
-
# @param target can be one of a number of things. If String, then
-
# the target of this instruction is set to this. If an Instruction,
-
# then the Instruction is shallowly cloned (target and content are
-
# copied). If a Source, then the source is scanned and parsed for
-
# an Instruction declaration.
-
# @param content Must be either a String, or a Parent. Can only
-
# be a Parent if the target argument is a Source. Otherwise, this
-
# String is set as the content of this instruction.
-
1
def initialize(target, content=nil)
-
if target.kind_of? String
-
super()
-
@target = target
-
@content = content
-
elsif target.kind_of? Instruction
-
super(content)
-
@target = target.target
-
@content = target.content
-
end
-
@content.strip! if @content
-
end
-
-
1
def clone
-
Instruction.new self
-
end
-
-
# == DEPRECATED
-
# See the rexml/formatters package
-
#
-
1
def write writer, indent=-1, transitive=false, ie_hack=false
-
Kernel.warn( "#{self.class.name}.write is deprecated" )
-
indent(writer, indent)
-
writer << START.sub(/\\/u, '')
-
writer << @target
-
writer << ' '
-
writer << @content
-
writer << STOP.sub(/\\/u, '')
-
end
-
-
# @return true if other is an Instruction, and the content and target
-
# of the other matches the target and content of this object.
-
1
def ==( other )
-
other.kind_of? Instruction and
-
other.target == @target and
-
other.content == @content
-
end
-
-
1
def node_type
-
:processing_instruction
-
end
-
-
1
def inspect
-
"<?p-i #{target} ...?>"
-
end
-
end
-
end
-
1
require 'rexml/xmltokens'
-
-
1
module REXML
-
# Adds named attributes to an object.
-
1
module Namespace
-
# The name of the object, valid if set
-
1
attr_reader :name, :expanded_name
-
# The expanded name of the object, valid if name is set
-
1
attr_accessor :prefix
-
1
include XMLTokens
-
1
NAMESPLIT = /^(?:(#{NCNAME_STR}):)?(#{NCNAME_STR})/u
-
-
# Sets the name and the expanded name
-
1
def name=( name )
-
126
@expanded_name = name
-
126
name =~ NAMESPLIT
-
126
if $1
-
@prefix = $1
-
else
-
126
@prefix = ""
-
126
@namespace = ""
-
end
-
126
@name = $2
-
end
-
-
# Compares names optionally WITH namespaces
-
1
def has_name?( other, ns=nil )
-
if ns
-
return (namespace() == ns and name() == other)
-
elsif other.include? ":"
-
return fully_expanded_name == other
-
else
-
return name == other
-
end
-
end
-
-
1
alias :local_name :name
-
-
# Fully expand the name, even if the prefix wasn't specified in the
-
# source file.
-
1
def fully_expanded_name
-
ns = prefix
-
return "#{ns}:#@name" if ns.size > 0
-
return @name
-
end
-
end
-
end
-
1
require "rexml/parseexception"
-
1
require "rexml/formatters/pretty"
-
1
require "rexml/formatters/default"
-
-
1
module REXML
-
# Represents a node in the tree. Nodes are never encountered except as
-
# superclasses of other objects. Nodes have siblings.
-
1
module Node
-
# @return the next sibling (nil if unset)
-
1
def next_sibling_node
-
return nil if @parent.nil?
-
@parent[ @parent.index(self) + 1 ]
-
end
-
-
# @return the previous sibling (nil if unset)
-
1
def previous_sibling_node
-
return nil if @parent.nil?
-
ind = @parent.index(self)
-
return nil if ind == 0
-
@parent[ ind - 1 ]
-
end
-
-
# indent::
-
# *DEPRECATED* This parameter is now ignored. See the formatters in the
-
# REXML::Formatters package for changing the output style.
-
1
def to_s indent=nil
-
unless indent.nil?
-
Kernel.warn( "#{self.class.name}.to_s(indent) parameter is deprecated" )
-
f = REXML::Formatters::Pretty.new( indent )
-
f.write( self, rv = "" )
-
else
-
f = REXML::Formatters::Default.new
-
f.write( self, rv = "" )
-
end
-
return rv
-
end
-
-
1
def indent to, ind
-
if @parent and @parent.context and not @parent.context[:indentstyle].nil? then
-
indentstyle = @parent.context[:indentstyle]
-
else
-
indentstyle = ' '
-
end
-
to << indentstyle*ind unless ind<1
-
end
-
-
1
def parent?
-
false;
-
end
-
-
-
# Visit all subnodes of +self+ recursively
-
1
def each_recursive(&block) # :yields: node
-
self.elements.each {|node|
-
block.call(node)
-
node.each_recursive(&block)
-
}
-
end
-
-
# Find (and return) first subnode (recursively) for which the block
-
# evaluates to true. Returns +nil+ if none was found.
-
1
def find_first_recursive(&block) # :yields: node
-
each_recursive {|node|
-
return node if block.call(node)
-
}
-
return nil
-
end
-
-
# Returns the position that +self+ holds in its parent's array, indexed
-
# from 1.
-
1
def index_in_parent
-
parent.index(self)+1
-
end
-
end
-
end
-
1
require 'rexml/encoding'
-
-
1
module REXML
-
1
class Output
-
1
include Encoding
-
-
1
attr_reader :encoding
-
-
1
def initialize real_IO, encd="iso-8859-1"
-
@output = real_IO
-
self.encoding = encd
-
-
@to_utf = encd != 'UTF-8'
-
end
-
-
1
def <<( content )
-
@output << (@to_utf ? self.encode(content) : content)
-
end
-
-
1
def to_s
-
"Output[#{encoding}]"
-
end
-
end
-
end
-
1
require "rexml/child"
-
-
1
module REXML
-
# A parent has children, and has methods for accessing them. The Parent
-
# class is never encountered except as the superclass for some other
-
# object.
-
1
class Parent < Child
-
1
include Enumerable
-
-
# Constructor
-
# @param parent if supplied, will be set as the parent of this object
-
1
def initialize parent=nil
-
87
super(parent)
-
87
@children = []
-
end
-
-
1
def add( object )
-
#puts "PARENT GOTS #{size} CHILDREN"
-
137
object.parent = self
-
137
@children << object
-
#puts "PARENT NOW GOTS #{size} CHILDREN"
-
137
object
-
end
-
-
1
alias :push :add
-
1
alias :<< :push
-
-
1
def unshift( object )
-
object.parent = self
-
@children.unshift object
-
end
-
-
1
def delete( object )
-
found = false
-
@children.delete_if {|c| c.equal?(object) and found = true }
-
object.parent = nil if found
-
found ? object : nil
-
end
-
-
1
def each(&block)
-
210
@children.each(&block)
-
end
-
-
1
def delete_if( &block )
-
@children.delete_if(&block)
-
end
-
-
1
def delete_at( index )
-
@children.delete_at index
-
end
-
-
1
def each_index( &block )
-
@children.each_index(&block)
-
end
-
-
# Fetches a child at a given index
-
# @param index the Integer index of the child to fetch
-
1
def []( index )
-
74
@children[index]
-
end
-
-
1
alias :each_child :each
-
-
-
-
# Set an index entry. See Array.[]=
-
# @param index the index of the element to set
-
# @param opt either the object to set, or an Integer length
-
# @param child if opt is an Integer, this is the child to set
-
# @return the parent (self)
-
1
def []=( *args )
-
args[-1].parent = self
-
@children[*args[0..-2]] = args[-1]
-
end
-
-
# Inserts an child before another child
-
# @param child1 this is either an xpath or an Element. If an Element,
-
# child2 will be inserted before child1 in the child list of the parent.
-
# If an xpath, child2 will be inserted before the first child to match
-
# the xpath.
-
# @param child2 the child to insert
-
# @return the parent (self)
-
1
def insert_before( child1, child2 )
-
if child1.kind_of? String
-
child1 = XPath.first( self, child1 )
-
child1.parent.insert_before child1, child2
-
else
-
ind = index(child1)
-
child2.parent.delete(child2) if child2.parent
-
@children[ind,0] = child2
-
child2.parent = self
-
end
-
self
-
end
-
-
# Inserts an child after another child
-
# @param child1 this is either an xpath or an Element. If an Element,
-
# child2 will be inserted after child1 in the child list of the parent.
-
# If an xpath, child2 will be inserted after the first child to match
-
# the xpath.
-
# @param child2 the child to insert
-
# @return the parent (self)
-
1
def insert_after( child1, child2 )
-
if child1.kind_of? String
-
child1 = XPath.first( self, child1 )
-
child1.parent.insert_after child1, child2
-
else
-
ind = index(child1)+1
-
child2.parent.delete(child2) if child2.parent
-
@children[ind,0] = child2
-
child2.parent = self
-
end
-
self
-
end
-
-
1
def to_a
-
17
@children.dup
-
end
-
-
# Fetches the index of a given child
-
# @param child the child to get the index of
-
# @return the index of the child, or nil if the object is not a child
-
# of this parent.
-
1
def index( child )
-
count = -1
-
@children.find { |i| count += 1 ; i.hash == child.hash }
-
count
-
end
-
-
# @return the number of children of this parent
-
1
def size
-
@children.size
-
end
-
-
1
alias :length :size
-
-
# Replaces one child with another, making sure the nodelist is correct
-
# @param to_replace the child to replace (must be a Child)
-
# @param replacement the child to insert into the nodelist (must be a
-
# Child)
-
1
def replace_child( to_replace, replacement )
-
@children.map! {|c| c.equal?( to_replace ) ? replacement : c }
-
to_replace.parent = nil
-
replacement.parent = self
-
end
-
-
# Deeply clones this object. This creates a complete duplicate of this
-
# Parent, including all descendants.
-
1
def deep_clone
-
cl = clone()
-
each do |child|
-
if child.kind_of? Parent
-
cl << child.deep_clone
-
else
-
cl << child.clone
-
end
-
end
-
cl
-
end
-
-
1
alias :children :to_a
-
-
1
def parent?
-
true
-
end
-
end
-
end
-
1
module REXML
-
1
class ParseException < RuntimeError
-
1
attr_accessor :source, :parser, :continued_exception
-
-
1
def initialize( message, source=nil, parser=nil, exception=nil )
-
8
super(message)
-
8
@source = source
-
8
@parser = parser
-
8
@continued_exception = exception
-
end
-
-
1
def to_s
-
# Quote the original exception, if there was one
-
16
if @continued_exception
-
6
err = @continued_exception.inspect
-
6
err << "\n"
-
6
err << @continued_exception.backtrace.join("\n")
-
6
err << "\n...\n"
-
else
-
10
err = ""
-
end
-
-
# Get the stack trace and error message
-
16
err << super
-
-
# Add contextual information
-
16
if @source
-
16
err << "\nLine: #{line}\n"
-
16
err << "Position: #{position}\n"
-
16
err << "Last 80 unconsumed characters:\n"
-
16
err << @source.buffer[0..80].force_encoding("ASCII-8BIT").gsub(/\n/, ' ')
-
end
-
-
16
err
-
end
-
-
1
def position
-
16
@source.current_line[0] if @source and defined? @source.current_line and
-
@source.current_line
-
end
-
-
1
def line
-
16
@source.current_line[2] if @source and defined? @source.current_line and
-
@source.current_line
-
end
-
-
1
def context
-
@source.current_line
-
end
-
end
-
end
-
1
require 'rexml/parseexception'
-
1
require 'rexml/undefinednamespaceexception'
-
1
require 'rexml/source'
-
1
require 'set'
-
-
1
module REXML
-
1
module Parsers
-
# = Using the Pull Parser
-
# <em>This API is experimental, and subject to change.</em>
-
# parser = PullParser.new( "<a>text<b att='val'/>txet</a>" )
-
# while parser.has_next?
-
# res = parser.next
-
# puts res[1]['att'] if res.start_tag? and res[0] == 'b'
-
# end
-
# See the PullEvent class for information on the content of the results.
-
# The data is identical to the arguments passed for the various events to
-
# the StreamListener API.
-
#
-
# Notice that:
-
# parser = PullParser.new( "<a>BAD DOCUMENT" )
-
# while parser.has_next?
-
# res = parser.next
-
# raise res[1] if res.error?
-
# end
-
#
-
# Nat Price gave me some good ideas for the API.
-
1
class BaseParser
-
1
LETTER = '[:alpha:]'
-
1
DIGIT = '[:digit:]'
-
-
1
COMBININGCHAR = '' # TODO
-
1
EXTENDER = '' # TODO
-
-
1
NCNAME_STR= "[#{LETTER}_:][-[:alnum:]._:#{COMBININGCHAR}#{EXTENDER}]*"
-
1
NAME_STR= "(?:(#{NCNAME_STR}):)?(#{NCNAME_STR})"
-
1
UNAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}"
-
-
1
NAMECHAR = '[\-\w\.:]'
-
1
NAME = "([\\w:]#{NAMECHAR}*)"
-
1
NMTOKEN = "(?:#{NAMECHAR})+"
-
1
NMTOKENS = "#{NMTOKEN}(\\s+#{NMTOKEN})*"
-
1
REFERENCE = "&(?:#{NAME};|#\\d+;|#x[0-9a-fA-F]+;)"
-
1
REFERENCE_RE = /#{REFERENCE}/
-
-
1
DOCTYPE_START = /\A\s*<!DOCTYPE\s/um
-
1
DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um
-
1
ATTRIBUTE_PATTERN = /\s*(#{NAME_STR})\s*=\s*(["'])(.*?)\4/um
-
1
COMMENT_START = /\A<!--/u
-
1
COMMENT_PATTERN = /<!--(.*?)-->/um
-
1
CDATA_START = /\A<!\[CDATA\[/u
-
1
CDATA_END = /^\s*\]\s*>/um
-
1
CDATA_PATTERN = /<!\[CDATA\[(.*?)\]\]>/um
-
1
XMLDECL_START = /\A<\?xml\s/u;
-
1
XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>/um
-
1
INSTRUCTION_START = /\A<\?/u
-
1
INSTRUCTION_PATTERN = /<\?(.*?)(\s+.*?)?\?>/um
-
1
TAG_MATCH = /^<((?>#{NAME_STR}))\s*((?>\s+#{UNAME_STR}\s*=\s*(["']).*?\5)*)\s*(\/)?>/um
-
1
CLOSE_MATCH = /^\s*<\/(#{NAME_STR})\s*>/um
-
-
1
VERSION = /\bversion\s*=\s*["'](.*?)['"]/um
-
1
ENCODING = /\bencoding\s*=\s*["'](.*?)['"]/um
-
1
STANDALONE = /\bstandalone\s*=\s*["'](.*?)['"]/um
-
-
1
ENTITY_START = /^\s*<!ENTITY/
-
1
IDENTITY = /^([!\*\w\-]+)(\s+#{NCNAME_STR})?(\s+["'](.*?)['"])?(\s+['"](.*?)["'])?/u
-
1
ELEMENTDECL_START = /^\s*<!ELEMENT/um
-
1
ELEMENTDECL_PATTERN = /^\s*(<!ELEMENT.*?)>/um
-
1
SYSTEMENTITY = /^\s*(%.*?;)\s*$/um
-
1
ENUMERATION = "\\(\\s*#{NMTOKEN}(?:\\s*\\|\\s*#{NMTOKEN})*\\s*\\)"
-
1
NOTATIONTYPE = "NOTATION\\s+\\(\\s*#{NAME}(?:\\s*\\|\\s*#{NAME})*\\s*\\)"
-
1
ENUMERATEDTYPE = "(?:(?:#{NOTATIONTYPE})|(?:#{ENUMERATION}))"
-
1
ATTTYPE = "(CDATA|ID|IDREF|IDREFS|ENTITY|ENTITIES|NMTOKEN|NMTOKENS|#{ENUMERATEDTYPE})"
-
1
ATTVALUE = "(?:\"((?:[^<&\"]|#{REFERENCE})*)\")|(?:'((?:[^<&']|#{REFERENCE})*)')"
-
1
DEFAULTDECL = "(#REQUIRED|#IMPLIED|(?:(#FIXED\\s+)?#{ATTVALUE}))"
-
1
ATTDEF = "\\s+#{NAME}\\s+#{ATTTYPE}\\s+#{DEFAULTDECL}"
-
1
ATTDEF_RE = /#{ATTDEF}/
-
1
ATTLISTDECL_START = /^\s*<!ATTLIST/um
-
1
ATTLISTDECL_PATTERN = /^\s*<!ATTLIST\s+#{NAME}(?:#{ATTDEF})*\s*>/um
-
1
NOTATIONDECL_START = /^\s*<!NOTATION/um
-
1
PUBLIC = /^\s*<!NOTATION\s+(\w[\-\w]*)\s+(PUBLIC)\s+(["'])(.*?)\3(?:\s+(["'])(.*?)\5)?\s*>/um
-
1
SYSTEM = /^\s*<!NOTATION\s+(\w[\-\w]*)\s+(SYSTEM)\s+(["'])(.*?)\3\s*>/um
-
-
1
TEXT_PATTERN = /\A([^<]*)/um
-
-
# Entity constants
-
1
PUBIDCHAR = "\x20\x0D\x0Aa-zA-Z0-9\\-()+,./:=?;!*@$_%#"
-
1
SYSTEMLITERAL = %Q{((?:"[^"]*")|(?:'[^']*'))}
-
1
PUBIDLITERAL = %Q{("[#{PUBIDCHAR}']*"|'[#{PUBIDCHAR}]*')}
-
1
EXTERNALID = "(?:(?:(SYSTEM)\\s+#{SYSTEMLITERAL})|(?:(PUBLIC)\\s+#{PUBIDLITERAL}\\s+#{SYSTEMLITERAL}))"
-
1
NDATADECL = "\\s+NDATA\\s+#{NAME}"
-
1
PEREFERENCE = "%#{NAME};"
-
1
ENTITYVALUE = %Q{((?:"(?:[^%&"]|#{PEREFERENCE}|#{REFERENCE})*")|(?:'([^%&']|#{PEREFERENCE}|#{REFERENCE})*'))}
-
1
PEDEF = "(?:#{ENTITYVALUE}|#{EXTERNALID})"
-
1
ENTITYDEF = "(?:#{ENTITYVALUE}|(?:#{EXTERNALID}(#{NDATADECL})?))"
-
1
PEDECL = "<!ENTITY\\s+(%)\\s+#{NAME}\\s+#{PEDEF}\\s*>"
-
1
GEDECL = "<!ENTITY\\s+#{NAME}\\s+#{ENTITYDEF}\\s*>"
-
1
ENTITYDECL = /\s*(?:#{GEDECL})|(?:#{PEDECL})/um
-
-
1
EREFERENCE = /&(?!#{NAME};)/
-
-
1
DEFAULT_ENTITIES = {
-
'gt' => [/>/, '>', '>', />/],
-
'lt' => [/</, '<', '<', /</],
-
'quot' => [/"/, '"', '"', /"/],
-
"apos" => [/'/, "'", "'", /'/]
-
}
-
-
-
######################################################################
-
# These are patterns to identify common markup errors, to make the
-
# error messages more informative.
-
######################################################################
-
1
MISSING_ATTRIBUTE_QUOTES = /^<#{NAME_STR}\s+#{NAME_STR}\s*=\s*[^"']/um
-
-
1
def initialize( source )
-
24
self.stream = source
-
end
-
-
1
def add_listener( listener )
-
if !defined?(@listeners) or !@listeners
-
@listeners = []
-
instance_eval <<-EOL
-
alias :_old_pull :pull
-
def pull
-
event = _old_pull
-
@listeners.each do |listener|
-
listener.receive event
-
end
-
event
-
end
-
EOL
-
end
-
@listeners << listener
-
end
-
-
1
attr_reader :source
-
-
1
def stream=( source )
-
24
@source = SourceFactory.create_from( source )
-
24
@closed = nil
-
24
@document_status = nil
-
24
@tags = []
-
24
@stack = []
-
24
@entities = []
-
24
@nsstack = []
-
end
-
-
1
def position
-
if @source.respond_to? :position
-
@source.position
-
else
-
# FIXME
-
0
-
end
-
end
-
-
# Returns true if there are no more events
-
1
def empty?
-
220
return (@source.empty? and @stack.empty?)
-
end
-
-
# Returns true if there are more events. Synonymous with !empty?
-
1
def has_next?
-
return !(@source.empty? and @stack.empty?)
-
end
-
-
# Push an event back on the head of the stream. This method
-
# has (theoretically) infinite depth.
-
1
def unshift token
-
@stack.unshift(token)
-
end
-
-
# Peek at the +depth+ event in the stack. The first element on the stack
-
# is at depth 0. If +depth+ is -1, will parse to the end of the input
-
# stream and return the last event, which is always :end_document.
-
# Be aware that this causes the stream to be parsed up to the +depth+
-
# event, so you can effectively pre-parse the entire document (pull the
-
# entire thing into memory) using this method.
-
1
def peek depth=0
-
raise %Q[Illegal argument "#{depth}"] if depth < -1
-
temp = []
-
if depth == -1
-
temp.push(pull()) until empty?
-
else
-
while @stack.size+temp.size < depth+1
-
temp.push(pull())
-
end
-
end
-
@stack += temp if temp.size > 0
-
@stack[depth]
-
end
-
-
# Returns the next event. This is a +PullEvent+ object.
-
1
def pull
-
220
if @closed
-
x, @closed = @closed, nil
-
return [ :end_element, x ]
-
end
-
220
return [ :end_document ] if empty?
-
200
return @stack.shift if @stack.size > 0
-
#STDERR.puts @source.encoding
-
200
@source.read if @source.buffer.size<2
-
#STDERR.puts "BUFFER = #{@source.buffer.inspect}"
-
200
if @document_status == nil
-
#@source.consume( /^\s*/um )
-
28
word = @source.match( /^((?:\s+)|(?:<[^>]*>))/um )
-
28
word = word[1] unless word.nil?
-
#STDERR.puts "WORD = #{word.inspect}"
-
28
case word
-
when COMMENT_START
-
return [ :comment, @source.match( COMMENT_PATTERN, true )[1] ]
-
when XMLDECL_START
-
#STDERR.puts "XMLDECL"
-
results = @source.match( XMLDECL_PATTERN, true )[1]
-
version = VERSION.match( results )
-
version = version[1] unless version.nil?
-
encoding = ENCODING.match(results)
-
encoding = encoding[1] unless encoding.nil?
-
@source.encoding = encoding
-
standalone = STANDALONE.match(results)
-
standalone = standalone[1] unless standalone.nil?
-
return [ :xmldecl, version, encoding, standalone ]
-
when INSTRUCTION_START
-
return [ :processing_instruction, *@source.match(INSTRUCTION_PATTERN, true)[1,2] ]
-
when DOCTYPE_START
-
md = @source.match( DOCTYPE_PATTERN, true )
-
@nsstack.unshift(curr_ns=Set.new)
-
identity = md[1]
-
close = md[2]
-
identity =~ IDENTITY
-
name = $1
-
raise REXML::ParseException.new("DOCTYPE is missing a name") if name.nil?
-
pub_sys = $2.nil? ? nil : $2.strip
-
long_name = $4.nil? ? nil : $4.strip
-
uri = $6.nil? ? nil : $6.strip
-
args = [ :start_doctype, name, pub_sys, long_name, uri ]
-
if close == ">"
-
@document_status = :after_doctype
-
@source.read if @source.buffer.size<2
-
md = @source.match(/^\s*/um, true)
-
@stack << [ :end_doctype ]
-
else
-
@document_status = :in_doctype
-
end
-
return args
-
when /^\s+/
-
else
-
24
@document_status = :after_doctype
-
24
@source.read if @source.buffer.size<2
-
24
md = @source.match(/\s*/um, true)
-
24
if @source.encoding == "UTF-8"
-
24
@source.buffer.force_encoding(::Encoding::UTF_8)
-
end
-
end
-
end
-
200
if @document_status == :in_doctype
-
md = @source.match(/\s*(.*?>)/um)
-
case md[1]
-
when SYSTEMENTITY
-
match = @source.match( SYSTEMENTITY, true )[1]
-
return [ :externalentity, match ]
-
-
when ELEMENTDECL_START
-
return [ :elementdecl, @source.match( ELEMENTDECL_PATTERN, true )[1] ]
-
-
when ENTITY_START
-
match = @source.match( ENTITYDECL, true ).to_a.compact
-
match[0] = :entitydecl
-
ref = false
-
if match[1] == '%'
-
ref = true
-
match.delete_at 1
-
end
-
# Now we have to sort out what kind of entity reference this is
-
if match[2] == 'SYSTEM'
-
# External reference
-
match[3] = match[3][1..-2] # PUBID
-
match.delete_at(4) if match.size > 4 # Chop out NDATA decl
-
# match is [ :entity, name, SYSTEM, pubid(, ndata)? ]
-
elsif match[2] == 'PUBLIC'
-
# External reference
-
match[3] = match[3][1..-2] # PUBID
-
match[4] = match[4][1..-2] # HREF
-
# match is [ :entity, name, PUBLIC, pubid, href ]
-
else
-
match[2] = match[2][1..-2]
-
match.pop if match.size == 4
-
# match is [ :entity, name, value ]
-
end
-
match << '%' if ref
-
return match
-
when ATTLISTDECL_START
-
md = @source.match( ATTLISTDECL_PATTERN, true )
-
raise REXML::ParseException.new( "Bad ATTLIST declaration!", @source ) if md.nil?
-
element = md[1]
-
contents = md[0]
-
-
pairs = {}
-
values = md[0].scan( ATTDEF_RE )
-
values.each do |attdef|
-
unless attdef[3] == "#IMPLIED"
-
attdef.compact!
-
val = attdef[3]
-
val = attdef[4] if val == "#FIXED "
-
pairs[attdef[0]] = val
-
if attdef[0] =~ /^xmlns:(.*)/
-
@nsstack[0] << $1
-
end
-
end
-
end
-
return [ :attlistdecl, element, pairs, contents ]
-
when NOTATIONDECL_START
-
md = nil
-
if @source.match( PUBLIC )
-
md = @source.match( PUBLIC, true )
-
vals = [md[1],md[2],md[4],md[6]]
-
elsif @source.match( SYSTEM )
-
md = @source.match( SYSTEM, true )
-
vals = [md[1],md[2],nil,md[4]]
-
else
-
raise REXML::ParseException.new( "error parsing notation: no matching pattern", @source )
-
end
-
return [ :notationdecl, *vals ]
-
when CDATA_END
-
@document_status = :after_doctype
-
@source.match( CDATA_END, true )
-
return [ :end_doctype ]
-
end
-
end
-
200
begin
-
200
if @source.buffer[0] == ?<
-
126
if @source.buffer[1] == ?/
-
63
@nsstack.shift
-
63
last_tag = @tags.pop
-
#md = @source.match_to_consume( '>', CLOSE_MATCH)
-
63
md = @source.match( CLOSE_MATCH, true )
-
raise REXML::ParseException.new( "Missing end tag for "+
-
"'#{last_tag}' (got \"#{md[1]}\")",
-
63
@source) unless last_tag == md[1]
-
59
return [ :end_element, last_tag ]
-
elsif @source.buffer[1] == ?!
-
md = @source.match(/\A(\s*[^>]*>)/um)
-
#STDERR.puts "SOURCE BUFFER = #{source.buffer}, #{source.buffer.size}"
-
raise REXML::ParseException.new("Malformed node", @source) unless md
-
if md[0][2] == ?-
-
md = @source.match( COMMENT_PATTERN, true )
-
-
case md[1]
-
when /--/, /-$/
-
raise REXML::ParseException.new("Malformed comment", @source)
-
end
-
-
return [ :comment, md[1] ] if md
-
else
-
md = @source.match( CDATA_PATTERN, true )
-
return [ :cdata, md[1] ] if md
-
end
-
raise REXML::ParseException.new( "Declarations can only occur "+
-
"in the doctype declaration.", @source)
-
elsif @source.buffer[1] == ??
-
md = @source.match( INSTRUCTION_PATTERN, true )
-
return [ :processing_instruction, md[1], md[2] ] if md
-
raise REXML::ParseException.new( "Bad instruction declaration",
-
@source)
-
else
-
# Get the next tag
-
63
md = @source.match(TAG_MATCH, true)
-
63
unless md
-
# Check for missing attribute quotes
-
raise REXML::ParseException.new("missing attribute quote", @source) if @source.match(MISSING_ATTRIBUTE_QUOTES )
-
raise REXML::ParseException.new("malformed XML: missing tag start", @source)
-
end
-
63
attributes = {}
-
63
prefixes = Set.new
-
63
prefixes << md[2] if md[2]
-
63
@nsstack.unshift(curr_ns=Set.new)
-
63
if md[4].size > 0
-
23
attrs = md[4].scan( ATTRIBUTE_PATTERN )
-
23
raise REXML::ParseException.new( "error parsing attributes: [#{attrs.join ', '}], excess = \"#$'\"", @source) if $' and $'.strip.size > 0
-
23
attrs.each { |a,b,c,d,e|
-
39
if b == "xmlns"
-
if c == "xml"
-
if d != "http://www.w3.org/XML/1998/namespace"
-
msg = "The 'xml' prefix must not be bound to any other namespace "+
-
"(http://www.w3.org/TR/REC-xml-names/#ns-decl)"
-
raise REXML::ParseException.new( msg, @source, self )
-
end
-
elsif c == "xmlns"
-
msg = "The 'xmlns' prefix must not be declared "+
-
"(http://www.w3.org/TR/REC-xml-names/#ns-decl)"
-
raise REXML::ParseException.new( msg, @source, self)
-
end
-
curr_ns << c
-
elsif b
-
prefixes << b unless b == "xml"
-
end
-
-
39
if attributes.has_key? a
-
msg = "Duplicate attribute #{a.inspect}"
-
raise REXML::ParseException.new( msg, @source, self)
-
end
-
-
39
attributes[a] = e
-
}
-
end
-
-
# Verify that all of the prefixes have been defined
-
63
for prefix in prefixes
-
unless @nsstack.find{|k| k.member?(prefix)}
-
raise UndefinedNamespaceException.new(prefix,@source,self)
-
end
-
end
-
-
63
if md[6]
-
@closed = md[1]
-
@nsstack.shift
-
else
-
63
@tags.push( md[1] )
-
end
-
63
return [ :start_element, md[1], attributes ]
-
end
-
else
-
74
md = @source.match( TEXT_PATTERN, true )
-
74
if md[0].length == 0
-
@source.match( /(\s+)/, true )
-
end
-
#STDERR.puts "GOT #{md[1].inspect}" unless md[0].length == 0
-
#return [ :text, "" ] if md[0].length == 0
-
# unnormalized = Text::unnormalize( md[1], self )
-
# return PullEvent.new( :text, md[1], unnormalized )
-
74
return [ :text, md[1] ]
-
end
-
rescue REXML::UndefinedNamespaceException
-
raise
-
rescue REXML::ParseException
-
4
raise
-
rescue Exception, NameError => error
-
raise REXML::ParseException.new( "Exception parsing",
-
@source, self, (error ? error : $!) )
-
end
-
return [ :dummy ]
-
end
-
-
1
def entity( reference, entities )
-
value = nil
-
value = entities[ reference ] if entities
-
if not value
-
value = DEFAULT_ENTITIES[ reference ]
-
value = value[2] if value
-
end
-
unnormalize( value, entities ) if value
-
end
-
-
# Escapes all possible entities
-
1
def normalize( input, entities=nil, entity_filter=nil )
-
copy = input.clone
-
# Doing it like this rather than in a loop improves the speed
-
copy.gsub!( EREFERENCE, '&' )
-
entities.each do |key, value|
-
copy.gsub!( value, "&#{key};" ) unless entity_filter and
-
entity_filter.include?(entity)
-
end if entities
-
copy.gsub!( EREFERENCE, '&' )
-
DEFAULT_ENTITIES.each do |key, value|
-
copy.gsub!( value[3], value[1] )
-
end
-
copy
-
end
-
-
# Unescapes all possible entities
-
1
def unnormalize( string, entities=nil, filter=nil )
-
rv = string.clone
-
rv.gsub!( /\r\n?/, "\n" )
-
matches = rv.scan( REFERENCE_RE )
-
return rv if matches.size == 0
-
rv.gsub!( /�*((?:\d+)|(?:x[a-fA-F0-9]+));/ ) {
-
m=$1
-
m = "0#{m}" if m[0] == ?x
-
[Integer(m)].pack('U*')
-
}
-
matches.collect!{|x|x[0]}.compact!
-
if matches.size > 0
-
matches.each do |entity_reference|
-
unless filter and filter.include?(entity_reference)
-
entity_value = entity( entity_reference, entities )
-
if entity_value
-
re = /&#{entity_reference};/
-
rv.gsub!( re, entity_value )
-
else
-
er = DEFAULT_ENTITIES[entity_reference]
-
rv.gsub!( er[0], er[2] ) if er
-
end
-
end
-
end
-
rv.gsub!( /&/, '&' )
-
end
-
rv
-
end
-
end
-
end
-
end
-
-
=begin
-
case event[0]
-
when :start_element
-
when :text
-
when :end_element
-
when :processing_instruction
-
when :cdata
-
when :comment
-
when :xmldecl
-
when :start_doctype
-
when :end_doctype
-
when :externalentity
-
when :elementdecl
-
when :entity
-
when :attlistdecl
-
when :notationdecl
-
when :end_doctype
-
end
-
=end
-
1
module REXML
-
1
module Parsers
-
1
class StreamParser
-
1
def initialize source, listener
-
@listener = listener
-
@parser = BaseParser.new( source )
-
end
-
-
1
def add_listener( listener )
-
@parser.add_listener( listener )
-
end
-
-
1
def parse
-
# entity string
-
while true
-
event = @parser.pull
-
case event[0]
-
when :end_document
-
return
-
when :start_element
-
attrs = event[2].each do |n, v|
-
event[2][n] = @parser.unnormalize( v )
-
end
-
@listener.tag_start( event[1], attrs )
-
when :end_element
-
@listener.tag_end( event[1] )
-
when :text
-
normalized = @parser.unnormalize( event[1] )
-
@listener.text( normalized )
-
when :processing_instruction
-
@listener.instruction( *event[1,2] )
-
when :start_doctype
-
@listener.doctype( *event[1..-1] )
-
when :end_doctype
-
# FIXME: remove this condition for milestone:3.2
-
@listener.doctype_end if @listener.respond_to? :doctype_end
-
when :comment, :attlistdecl, :cdata, :xmldecl, :elementdecl
-
@listener.send( event[0].to_s, *event[1..-1] )
-
when :entitydecl, :notationdecl
-
@listener.send( event[0].to_s, event[1..-1] )
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'rexml/validation/validationexception'
-
1
require 'rexml/undefinednamespaceexception'
-
-
1
module REXML
-
1
module Parsers
-
1
class TreeParser
-
1
def initialize( source, build_context = Document.new )
-
24
@build_context = build_context
-
24
@parser = Parsers::BaseParser.new( source )
-
end
-
-
1
def add_listener( listener )
-
@parser.add_listener( listener )
-
end
-
-
1
def parse
-
24
tag_stack = []
-
24
in_doctype = false
-
24
entities = nil
-
24
begin
-
24
while true
-
220
event = @parser.pull
-
#STDERR.puts "TREEPARSER GOT #{event.inspect}"
-
216
case event[0]
-
when :end_document
-
20
unless tag_stack.empty?
-
#raise ParseException.new("No close tag for #{tag_stack.inspect}")
-
raise ParseException.new("No close tag for #{@build_context.xpath}")
-
end
-
20
return
-
when :start_element
-
63
tag_stack.push(event[1])
-
63
el = @build_context = @build_context.add_element( event[1] )
-
63
event[2].each do |key, value|
-
39
el.attributes[key]=Attribute.new(key,value,self)
-
end
-
when :end_element
-
59
tag_stack.pop
-
59
@build_context = @build_context.parent
-
when :text
-
74
if not in_doctype
-
74
if @build_context[-1].instance_of? Text
-
@build_context[-1] << event[1]
-
else
-
@build_context.add(
-
Text.new(event[1], @build_context.whitespace, nil, true)
-
) unless (
-
@build_context.ignore_whitespace_nodes and
-
74
event[1].strip.size==0
-
)
-
end
-
end
-
when :comment
-
c = Comment.new( event[1] )
-
@build_context.add( c )
-
when :cdata
-
c = CData.new( event[1] )
-
@build_context.add( c )
-
when :processing_instruction
-
@build_context.add( Instruction.new( event[1], event[2] ) )
-
when :end_doctype
-
in_doctype = false
-
entities.each { |k,v| entities[k] = @build_context.entities[k].value }
-
@build_context = @build_context.parent
-
when :start_doctype
-
doctype = DocType.new( event[1..-1], @build_context )
-
@build_context = doctype
-
entities = {}
-
in_doctype = true
-
when :attlistdecl
-
n = AttlistDecl.new( event[1..-1] )
-
@build_context.add( n )
-
when :externalentity
-
n = ExternalEntity.new( event[1] )
-
@build_context.add( n )
-
when :elementdecl
-
n = ElementDecl.new( event[1] )
-
@build_context.add(n)
-
when :entitydecl
-
entities[ event[1] ] = event[2] unless event[2] =~ /PUBLIC|SYSTEM/
-
@build_context.add(Entity.new(event))
-
when :notationdecl
-
n = NotationDecl.new( *event[1..-1] )
-
@build_context.add( n )
-
when :xmldecl
-
x = XMLDecl.new( event[1], event[2], event[3] )
-
@build_context.add( x )
-
end
-
end
-
4
rescue REXML::Validation::ValidationException
-
raise
-
rescue REXML::UndefinedNamespaceException
-
raise
-
rescue
-
4
raise ParseException.new( $!.message, @parser.source, @parser, $! )
-
end
-
end
-
end
-
end
-
end
-
1
require 'rexml/namespace'
-
1
require 'rexml/xmltokens'
-
-
1
module REXML
-
1
module Parsers
-
# You don't want to use this class. Really. Use XPath, which is a wrapper
-
# for this class. Believe me. You don't want to poke around in here.
-
# There is strange, dark magic at work in this code. Beware. Go back! Go
-
# back while you still can!
-
1
class XPathParser
-
1
include XMLTokens
-
1
LITERAL = /^'([^']*)'|^"([^"]*)"/u
-
-
1
def namespaces=( namespaces )
-
Functions::namespace_context = namespaces
-
@namespaces = namespaces
-
end
-
-
1
def parse path
-
17
path = path.dup
-
17
path.gsub!(/([\(\[])\s+/, '\1') # Strip ignorable spaces
-
17
path.gsub!( /\s+([\]\)])/, '\1')
-
17
parsed = []
-
17
path = OrExpr(path, parsed)
-
17
parsed
-
end
-
-
1
def predicate path
-
parsed = []
-
Predicate( "[#{path}]", parsed )
-
parsed
-
end
-
-
1
def abbreviate( path )
-
path = path.kind_of?(String) ? parse( path ) : path
-
string = ""
-
document = false
-
while path.size > 0
-
op = path.shift
-
case op
-
when :node
-
when :attribute
-
string << "/" if string.size > 0
-
string << "@"
-
when :child
-
string << "/" if string.size > 0
-
when :descendant_or_self
-
string << "/"
-
when :self
-
string << "."
-
when :parent
-
string << ".."
-
when :any
-
string << "*"
-
when :text
-
string << "text()"
-
when :following, :following_sibling,
-
:ancestor, :ancestor_or_self, :descendant,
-
:namespace, :preceding, :preceding_sibling
-
string << "/" unless string.size == 0
-
string << op.to_s.tr("_", "-")
-
string << "::"
-
when :qname
-
prefix = path.shift
-
name = path.shift
-
string << prefix+":" if prefix.size > 0
-
string << name
-
when :predicate
-
string << '['
-
string << predicate_to_string( path.shift ) {|x| abbreviate( x ) }
-
string << ']'
-
when :document
-
document = true
-
when :function
-
string << path.shift
-
string << "( "
-
string << predicate_to_string( path.shift[0] ) {|x| abbreviate( x )}
-
string << " )"
-
when :literal
-
string << %Q{ "#{path.shift}" }
-
else
-
string << "/" unless string.size == 0
-
string << "UNKNOWN("
-
string << op.inspect
-
string << ")"
-
end
-
end
-
string = "/"+string if document
-
return string
-
end
-
-
1
def expand( path )
-
path = path.kind_of?(String) ? parse( path ) : path
-
string = ""
-
document = false
-
while path.size > 0
-
op = path.shift
-
case op
-
when :node
-
string << "node()"
-
when :attribute, :child, :following, :following_sibling,
-
:ancestor, :ancestor_or_self, :descendant, :descendant_or_self,
-
:namespace, :preceding, :preceding_sibling, :self, :parent
-
string << "/" unless string.size == 0
-
string << op.to_s.tr("_", "-")
-
string << "::"
-
when :any
-
string << "*"
-
when :qname
-
prefix = path.shift
-
name = path.shift
-
string << prefix+":" if prefix.size > 0
-
string << name
-
when :predicate
-
string << '['
-
string << predicate_to_string( path.shift ) { |x| expand(x) }
-
string << ']'
-
when :document
-
document = true
-
else
-
string << "/" unless string.size == 0
-
string << "UNKNOWN("
-
string << op.inspect
-
string << ")"
-
end
-
end
-
string = "/"+string if document
-
return string
-
end
-
-
1
def predicate_to_string( path, &block )
-
string = ""
-
case path[0]
-
when :and, :or, :mult, :plus, :minus, :neq, :eq, :lt, :gt, :lteq, :gteq, :div, :mod, :union
-
op = path.shift
-
case op
-
when :eq
-
op = "="
-
when :lt
-
op = "<"
-
when :gt
-
op = ">"
-
when :lteq
-
op = "<="
-
when :gteq
-
op = ">="
-
when :neq
-
op = "!="
-
when :union
-
op = "|"
-
end
-
left = predicate_to_string( path.shift, &block )
-
right = predicate_to_string( path.shift, &block )
-
string << " "
-
string << left
-
string << " "
-
string << op.to_s
-
string << " "
-
string << right
-
string << " "
-
when :function
-
path.shift
-
name = path.shift
-
string << name
-
string << "( "
-
string << predicate_to_string( path.shift, &block )
-
string << " )"
-
when :literal
-
path.shift
-
string << " "
-
string << path.shift.inspect
-
string << " "
-
else
-
string << " "
-
string << yield( path )
-
string << " "
-
end
-
return string.squeeze(" ")
-
end
-
-
1
private
-
#LocationPath
-
# | RelativeLocationPath
-
# | '/' RelativeLocationPath?
-
# | '//' RelativeLocationPath
-
1
def LocationPath path, parsed
-
#puts "LocationPath '#{path}'"
-
17
path = path.strip
-
17
if path[0] == ?/
-
parsed << :document
-
if path[1] == ?/
-
parsed << :descendant_or_self
-
parsed << :node
-
path = path[2..-1]
-
else
-
path = path[1..-1]
-
end
-
end
-
#puts parsed.inspect
-
17
return RelativeLocationPath( path, parsed ) if path.size > 0
-
end
-
-
#RelativeLocationPath
-
# | Step
-
# | (AXIS_NAME '::' | '@' | '') AxisSpecifier
-
# NodeTest
-
# Predicate
-
# | '.' | '..' AbbreviatedStep
-
# | RelativeLocationPath '/' Step
-
# | RelativeLocationPath '//' Step
-
1
AXIS = /^(ancestor|ancestor-or-self|attribute|child|descendant|descendant-or-self|following|following-sibling|namespace|parent|preceding|preceding-sibling|self)::/
-
1
def RelativeLocationPath path, parsed
-
#puts "RelativeLocationPath #{path}"
-
17
while path.size > 0
-
# (axis or @ or <child::>) nodetest predicate >
-
# OR > / Step
-
# (. or ..) >
-
17
if path[0] == ?.
-
if path[1] == ?.
-
parsed << :parent
-
parsed << :node
-
path = path[2..-1]
-
else
-
parsed << :self
-
parsed << :node
-
path = path[1..-1]
-
end
-
else
-
17
if path[0] == ?@
-
#puts "ATTRIBUTE"
-
parsed << :attribute
-
path = path[1..-1]
-
# Goto Nodetest
-
elsif path =~ AXIS
-
parsed << $1.tr('-','_').intern
-
path = $'
-
# Goto Nodetest
-
else
-
17
parsed << :child
-
end
-
-
#puts "NODETESTING '#{path}'"
-
17
n = []
-
17
path = NodeTest( path, n)
-
#puts "NODETEST RETURNED '#{path}'"
-
-
17
if path[0] == ?[
-
path = Predicate( path, n )
-
end
-
-
17
parsed.concat(n)
-
end
-
-
17
if path.size > 0
-
if path[0] == ?/
-
if path[1] == ?/
-
parsed << :descendant_or_self
-
parsed << :node
-
path = path[2..-1]
-
else
-
path = path[1..-1]
-
end
-
else
-
return path
-
end
-
end
-
end
-
17
return path
-
end
-
-
# Returns a 1-1 map of the nodeset
-
# The contents of the resulting array are either:
-
# true/false, if a positive match
-
# String, if a name match
-
#NodeTest
-
# | ('*' | NCNAME ':' '*' | QNAME) NameTest
-
# | NODE_TYPE '(' ')' NodeType
-
# | PI '(' LITERAL ')' PI
-
# | '[' expr ']' Predicate
-
1
NCNAMETEST= /^(#{NCNAME_STR}):\*/u
-
1
QNAME = Namespace::NAMESPLIT
-
1
NODE_TYPE = /^(comment|text|node)\(\s*\)/m
-
1
PI = /^processing-instruction\(/
-
1
def NodeTest path, parsed
-
#puts "NodeTest with #{path}"
-
17
case path
-
when /^\*/
-
17
path = $'
-
17
parsed << :any
-
when NODE_TYPE
-
type = $1
-
path = $'
-
parsed << type.tr('-', '_').intern
-
when PI
-
path = $'
-
literal = nil
-
if path !~ /^\s*\)/
-
path =~ LITERAL
-
literal = $1
-
path = $'
-
raise ParseException.new("Missing ')' after processing instruction") if path[0] != ?)
-
path = path[1..-1]
-
end
-
parsed << :processing_instruction
-
parsed << (literal || '')
-
when NCNAMETEST
-
#puts "NCNAMETEST"
-
prefix = $1
-
path = $'
-
parsed << :namespace
-
parsed << prefix
-
when QNAME
-
#puts "QNAME"
-
prefix = $1
-
name = $2
-
path = $'
-
prefix = "" unless prefix
-
parsed << :qname
-
parsed << prefix
-
parsed << name
-
end
-
17
return path
-
end
-
-
# Filters the supplied nodeset on the predicate(s)
-
1
def Predicate path, parsed
-
#puts "PREDICATE with #{path}"
-
return nil unless path[0] == ?[
-
predicates = []
-
while path[0] == ?[
-
path, expr = get_group(path)
-
predicates << expr[1..-2] if expr
-
end
-
#puts "PREDICATES = #{predicates.inspect}"
-
predicates.each{ |pred|
-
#puts "ORING #{pred}"
-
preds = []
-
parsed << :predicate
-
parsed << preds
-
OrExpr(pred, preds)
-
}
-
#puts "PREDICATES = #{predicates.inspect}"
-
path
-
end
-
-
# The following return arrays of true/false, a 1-1 mapping of the
-
# supplied nodeset, except for axe(), which returns a filtered
-
# nodeset
-
-
#| OrExpr S 'or' S AndExpr
-
#| AndExpr
-
1
def OrExpr path, parsed
-
#puts "OR >>> #{path}"
-
17
n = []
-
17
rest = AndExpr( path, n )
-
#puts "OR <<< #{rest}"
-
17
if rest != path
-
17
while rest =~ /^\s*( or )/
-
n = [ :or, n, [] ]
-
rest = AndExpr( $', n[-1] )
-
end
-
end
-
17
if parsed.size == 0 and n.size != 0
-
17
parsed.replace(n)
-
elsif n.size > 0
-
parsed << n
-
end
-
17
rest
-
end
-
-
#| AndExpr S 'and' S EqualityExpr
-
#| EqualityExpr
-
1
def AndExpr path, parsed
-
#puts "AND >>> #{path}"
-
17
n = []
-
17
rest = EqualityExpr( path, n )
-
#puts "AND <<< #{rest}"
-
17
if rest != path
-
17
while rest =~ /^\s*( and )/
-
n = [ :and, n, [] ]
-
#puts "AND >>> #{rest}"
-
rest = EqualityExpr( $', n[-1] )
-
#puts "AND <<< #{rest}"
-
end
-
end
-
17
if parsed.size == 0 and n.size != 0
-
17
parsed.replace(n)
-
elsif n.size > 0
-
parsed << n
-
end
-
17
rest
-
end
-
-
#| EqualityExpr ('=' | '!=') RelationalExpr
-
#| RelationalExpr
-
1
def EqualityExpr path, parsed
-
#puts "EQUALITY >>> #{path}"
-
17
n = []
-
17
rest = RelationalExpr( path, n )
-
#puts "EQUALITY <<< #{rest}"
-
17
if rest != path
-
17
while rest =~ /^\s*(!?=)\s*/
-
if $1[0] == ?!
-
n = [ :neq, n, [] ]
-
else
-
n = [ :eq, n, [] ]
-
end
-
rest = RelationalExpr( $', n[-1] )
-
end
-
end
-
17
if parsed.size == 0 and n.size != 0
-
17
parsed.replace(n)
-
elsif n.size > 0
-
parsed << n
-
end
-
17
rest
-
end
-
-
#| RelationalExpr ('<' | '>' | '<=' | '>=') AdditiveExpr
-
#| AdditiveExpr
-
1
def RelationalExpr path, parsed
-
#puts "RELATION >>> #{path}"
-
17
n = []
-
17
rest = AdditiveExpr( path, n )
-
#puts "RELATION <<< #{rest}"
-
17
if rest != path
-
17
while rest =~ /^\s*([<>]=?)\s*/
-
if $1[0] == ?<
-
sym = "lt"
-
else
-
sym = "gt"
-
end
-
sym << "eq" if $1[-1] == ?=
-
n = [ sym.intern, n, [] ]
-
rest = AdditiveExpr( $', n[-1] )
-
end
-
end
-
17
if parsed.size == 0 and n.size != 0
-
17
parsed.replace(n)
-
elsif n.size > 0
-
parsed << n
-
end
-
17
rest
-
end
-
-
#| AdditiveExpr ('+' | S '-') MultiplicativeExpr
-
#| MultiplicativeExpr
-
1
def AdditiveExpr path, parsed
-
#puts "ADDITIVE >>> #{path}"
-
17
n = []
-
17
rest = MultiplicativeExpr( path, n )
-
#puts "ADDITIVE <<< #{rest}"
-
17
if rest != path
-
17
while rest =~ /^\s*(\+| -)\s*/
-
if $1[0] == ?+
-
n = [ :plus, n, [] ]
-
else
-
n = [ :minus, n, [] ]
-
end
-
rest = MultiplicativeExpr( $', n[-1] )
-
end
-
end
-
17
if parsed.size == 0 and n.size != 0
-
17
parsed.replace(n)
-
elsif n.size > 0
-
parsed << n
-
end
-
17
rest
-
end
-
-
#| MultiplicativeExpr ('*' | S ('div' | 'mod') S) UnaryExpr
-
#| UnaryExpr
-
1
def MultiplicativeExpr path, parsed
-
#puts "MULT >>> #{path}"
-
17
n = []
-
17
rest = UnaryExpr( path, n )
-
#puts "MULT <<< #{rest}"
-
17
if rest != path
-
17
while rest =~ /^\s*(\*| div | mod )\s*/
-
if $1[0] == ?*
-
n = [ :mult, n, [] ]
-
elsif $1.include?( "div" )
-
n = [ :div, n, [] ]
-
else
-
n = [ :mod, n, [] ]
-
end
-
rest = UnaryExpr( $', n[-1] )
-
end
-
end
-
17
if parsed.size == 0 and n.size != 0
-
17
parsed.replace(n)
-
elsif n.size > 0
-
parsed << n
-
end
-
17
rest
-
end
-
-
#| '-' UnaryExpr
-
#| UnionExpr
-
1
def UnaryExpr path, parsed
-
17
path =~ /^(\-*)/
-
17
path = $'
-
17
if $1 and (($1.size % 2) != 0)
-
mult = -1
-
else
-
17
mult = 1
-
end
-
17
parsed << :neg if mult < 0
-
-
#puts "UNARY >>> #{path}"
-
17
n = []
-
17
path = UnionExpr( path, n )
-
#puts "UNARY <<< #{path}"
-
17
parsed.concat( n )
-
17
path
-
end
-
-
#| UnionExpr '|' PathExpr
-
#| PathExpr
-
1
def UnionExpr path, parsed
-
#puts "UNION >>> #{path}"
-
17
n = []
-
17
rest = PathExpr( path, n )
-
#puts "UNION <<< #{rest}"
-
17
if rest != path
-
17
while rest =~ /^\s*(\|)\s*/
-
n = [ :union, n, [] ]
-
rest = PathExpr( $', n[-1] )
-
end
-
end
-
17
if parsed.size == 0 and n.size != 0
-
17
parsed.replace( n )
-
elsif n.size > 0
-
parsed << n
-
end
-
17
rest
-
end
-
-
#| LocationPath
-
#| FilterExpr ('/' | '//') RelativeLocationPath
-
1
def PathExpr path, parsed
-
17
path =~ /^\s*/
-
17
path = $'
-
#puts "PATH >>> #{path}"
-
17
n = []
-
17
rest = FilterExpr( path, n )
-
#puts "PATH <<< '#{rest}'"
-
17
if rest != path
-
if rest and rest[0] == ?/
-
return RelativeLocationPath(rest, n)
-
end
-
end
-
#puts "BEFORE WITH '#{rest}'"
-
17
rest = LocationPath(rest, n) if rest =~ /\A[\/\.\@\[\w*]/
-
17
parsed.concat(n)
-
17
return rest
-
end
-
-
#| FilterExpr Predicate
-
#| PrimaryExpr
-
1
def FilterExpr path, parsed
-
#puts "FILTER >>> #{path}"
-
17
n = []
-
17
path = PrimaryExpr( path, n )
-
#puts "FILTER <<< #{path}"
-
17
path = Predicate(path, n) if path and path[0] == ?[
-
#puts "FILTER <<< #{path}"
-
17
parsed.concat(n)
-
17
path
-
end
-
-
#| VARIABLE_REFERENCE
-
#| '(' expr ')'
-
#| LITERAL
-
#| NUMBER
-
#| FunctionCall
-
1
VARIABLE_REFERENCE = /^\$(#{NAME_STR})/u
-
1
NUMBER = /^(\d*\.?\d+)/
-
1
NT = /^comment|text|processing-instruction|node$/
-
1
def PrimaryExpr path, parsed
-
17
case path
-
when VARIABLE_REFERENCE
-
varname = $1
-
path = $'
-
parsed << :variable
-
parsed << varname
-
#arry << @variables[ varname ]
-
when /^(\w[-\w]*)(?:\()/
-
#puts "PrimaryExpr :: Function >>> #$1 -- '#$''"
-
fname = $1
-
tmp = $'
-
#puts "#{fname} =~ #{NT.inspect}"
-
return path if fname =~ NT
-
path = tmp
-
parsed << :function
-
parsed << fname
-
path = FunctionCall(path, parsed)
-
when NUMBER
-
#puts "LITERAL or NUMBER: #$1"
-
varname = $1.nil? ? $2 : $1
-
path = $'
-
parsed << :literal
-
parsed << (varname.include?('.') ? varname.to_f : varname.to_i)
-
when LITERAL
-
#puts "LITERAL or NUMBER: #$1"
-
varname = $1.nil? ? $2 : $1
-
path = $'
-
parsed << :literal
-
parsed << varname
-
when /^\(/ #/
-
path, contents = get_group(path)
-
contents = contents[1..-2]
-
n = []
-
OrExpr( contents, n )
-
parsed.concat(n)
-
end
-
17
path
-
end
-
-
#| FUNCTION_NAME '(' ( expr ( ',' expr )* )? ')'
-
1
def FunctionCall rest, parsed
-
path, arguments = parse_args(rest)
-
argset = []
-
for argument in arguments
-
args = []
-
OrExpr( argument, args )
-
argset << args
-
end
-
parsed << argset
-
path
-
end
-
-
# get_group( '[foo]bar' ) -> ['bar', '[foo]']
-
1
def get_group string
-
ind = 0
-
depth = 0
-
st = string[0,1]
-
en = (st == "(" ? ")" : "]")
-
begin
-
case string[ind,1]
-
when st
-
depth += 1
-
when en
-
depth -= 1
-
end
-
ind += 1
-
end while depth > 0 and ind < string.length
-
return nil unless depth==0
-
[string[ind..-1], string[0..ind-1]]
-
end
-
-
1
def parse_args( string )
-
arguments = []
-
ind = 0
-
inquot = false
-
inapos = false
-
depth = 1
-
begin
-
case string[ind]
-
when ?"
-
inquot = !inquot unless inapos
-
when ?'
-
inapos = !inapos unless inquot
-
else
-
unless inquot or inapos
-
case string[ind]
-
when ?(
-
depth += 1
-
if depth == 1
-
string = string[1..-1]
-
ind -= 1
-
end
-
when ?)
-
depth -= 1
-
if depth == 0
-
s = string[0,ind].strip
-
arguments << s unless s == ""
-
string = string[ind+1..-1]
-
end
-
when ?,
-
if depth == 1
-
s = string[0,ind].strip
-
arguments << s unless s == ""
-
string = string[ind+1..-1]
-
ind = -1
-
end
-
end
-
end
-
end
-
ind += 1
-
end while depth > 0 and ind < string.length
-
return nil unless depth==0
-
[string,arguments]
-
end
-
end
-
end
-
end
-
# -*- encoding: utf-8 -*-
-
# REXML is an XML toolkit for Ruby[http://www.ruby-lang.org], in Ruby.
-
#
-
# REXML is a _pure_ Ruby, XML 1.0 conforming,
-
# non-validating[http://www.w3.org/TR/2004/REC-xml-20040204/#sec-conformance]
-
# toolkit with an intuitive API. REXML passes 100% of the non-validating Oasis
-
# tests[http://www.oasis-open.org/committees/xml-conformance/xml-test-suite.shtml],
-
# and provides tree, stream, SAX2, pull, and lightweight APIs. REXML also
-
# includes a full XPath[http://www.w3c.org/tr/xpath] 1.0 implementation. Since
-
# Ruby 1.8, REXML is included in the standard Ruby distribution.
-
#
-
# Main page:: http://www.germane-software.com/software/rexml
-
# Author:: Sean Russell <serATgermaneHYPHENsoftwareDOTcom>
-
# Date:: 2008/019
-
# Version:: 3.1.7.3
-
#
-
# This API documentation can be downloaded from the REXML home page, or can
-
# be accessed online[http://www.germane-software.com/software/rexml_doc]
-
#
-
# A tutorial is available in the REXML distribution in docs/tutorial.html,
-
# or can be accessed
-
# online[http://www.germane-software.com/software/rexml/docs/tutorial.html]
-
1
module REXML
-
1
COPYRIGHT = "Copyright © 2001-2008 Sean Russell <ser@germane-software.com>"
-
1
DATE = "2008/019"
-
1
VERSION = "3.1.7.3"
-
1
REVISION = %w$Revision: 26193 $[1] || ''
-
-
1
Copyright = COPYRIGHT
-
1
Version = VERSION
-
end
-
1
require 'rexml/encoding'
-
-
1
module REXML
-
# Generates Source-s. USE THIS CLASS.
-
1
class SourceFactory
-
# Generates a Source object
-
# @param arg Either a String, or an IO
-
# @return a Source, or nil if a bad argument was given
-
1
def SourceFactory::create_from(arg)
-
if arg.respond_to? :read and
-
24
arg.respond_to? :readline and
-
arg.respond_to? :nil? and
-
arg.respond_to? :eof?
-
24
IOSource.new(arg)
-
elsif arg.respond_to? :to_str
-
require 'stringio'
-
IOSource.new(StringIO.new(arg))
-
elsif arg.kind_of? Source
-
arg
-
else
-
raise "#{arg.class} is not a valid input stream. It must walk \n"+
-
"like either a String, an IO, or a Source."
-
end
-
end
-
end
-
-
# A Source can be searched for patterns, and wraps buffers and other
-
# objects and provides consumption of text
-
1
class Source
-
1
include Encoding
-
# The current buffer (what we're going to read next)
-
1
attr_reader :buffer
-
# The line number of the last consumed text
-
1
attr_reader :line
-
1
attr_reader :encoding
-
-
# Constructor
-
# @param arg must be a String, and should be a valid XML document
-
# @param encoding if non-null, sets the encoding of the source to this
-
# value, overriding all encoding detection
-
1
def initialize(arg, encoding=nil)
-
24
@orig = @buffer = arg
-
24
if encoding
-
self.encoding = encoding
-
else
-
24
self.encoding = check_encoding( @buffer )
-
end
-
24
@line = 0
-
end
-
-
-
# Inherited from Encoding
-
# Overridden to support optimized en/decoding
-
1
def encoding=(enc)
-
24
return unless super
-
24
@line_break = encode( '>' )
-
24
if @encoding != 'UTF-8'
-
@buffer = decode(@buffer)
-
@to_utf = true
-
else
-
24
@to_utf = false
-
24
@buffer.force_encoding ::Encoding::UTF_8
-
end
-
end
-
-
# Scans the source for a given pattern. Note, that this is not your
-
# usual scan() method. For one thing, the pattern argument has some
-
# requirements; for another, the source can be consumed. You can easily
-
# confuse this method. Originally, the patterns were easier
-
# to construct and this method more robust, because this method
-
# generated search regexes on the fly; however, this was
-
# computationally expensive and slowed down the entire REXML package
-
# considerably, since this is by far the most commonly called method.
-
# @param pattern must be a Regexp, and must be in the form of
-
# /^\s*(#{your pattern, with no groups})(.*)/. The first group
-
# will be returned; the second group is used if the consume flag is
-
# set.
-
# @param consume if true, the pattern returned will be consumed, leaving
-
# everything after it in the Source.
-
# @return the pattern, if found, or nil if the Source is empty or the
-
# pattern is not found.
-
1
def scan(pattern, cons=false)
-
return nil if @buffer.nil?
-
rv = @buffer.scan(pattern)
-
@buffer = $' if cons and rv.size>0
-
rv
-
end
-
-
1
def read
-
end
-
-
1
def consume( pattern )
-
@buffer = $' if pattern.match( @buffer )
-
end
-
-
1
def match_to( char, pattern )
-
return pattern.match(@buffer)
-
end
-
-
1
def match_to_consume( char, pattern )
-
md = pattern.match(@buffer)
-
@buffer = $'
-
return md
-
end
-
-
1
def match(pattern, cons=false)
-
md = pattern.match(@buffer)
-
@buffer = $' if cons and md
-
return md
-
end
-
-
# @return true if the Source is exhausted
-
1
def empty?
-
220
@buffer == ""
-
end
-
-
1
def position
-
@orig.index( @buffer )
-
end
-
-
# @return the current line in the source
-
1
def current_line
-
lines = @orig.split
-
res = lines.grep @buffer[0..30]
-
res = res[-1] if res.kind_of? Array
-
lines.index( res ) if res
-
end
-
end
-
-
# A Source that wraps an IO. See the Source class for method
-
# documentation
-
1
class IOSource < Source
-
#attr_reader :block_size
-
-
# block_size has been deprecated
-
1
def initialize(arg, block_size=500, encoding=nil)
-
24
@er_source = @source = arg
-
24
@to_utf = false
-
-
# Determining the encoding is a deceptively difficult issue to resolve.
-
# First, we check the first two bytes for UTF-16. Then we
-
# assume that the encoding is at least ASCII enough for the '>', and
-
# we read until we get one of those. This gives us the XML declaration,
-
# if there is one. If there isn't one, the file MUST be UTF-8, as per
-
# the XML spec. If there is one, we can determine the encoding from
-
# it.
-
24
@buffer = ""
-
24
str = @source.read( 2 ) || ''
-
24
if encoding
-
self.encoding = encoding
-
elsif str[0,2] == "\xfe\xff"
-
@line_break = "\000>"
-
elsif str[0,2] == "\xff\xfe"
-
@line_break = ">\000"
-
elsif str[0,2] == "\xef\xbb"
-
str += @source.read(1)
-
str = '' if (str[2,1] == "\xBF")
-
@line_break = ">"
-
else
-
24
@line_break = ">"
-
end
-
24
super( @source.eof? ? str : str+@source.readline( @line_break ) )
-
-
if !@to_utf and
-
24
@buffer.respond_to?(:force_encoding) and
-
@source.respond_to?(:external_encoding) and
-
@source.external_encoding != ::Encoding::UTF_8
-
24
@force_utf8 = true
-
else
-
@force_utf8 = false
-
end
-
end
-
-
1
def scan(pattern, cons=false)
-
rv = super
-
# You'll notice that this next section is very similar to the same
-
# section in match(), but just a liiittle different. This is
-
# because it is a touch faster to do it this way with scan()
-
# than the way match() does it; enough faster to warrent duplicating
-
# some code
-
if rv.size == 0
-
until @buffer =~ pattern or @source.nil?
-
begin
-
@buffer << readline
-
rescue Iconv::IllegalSequence
-
raise
-
rescue
-
@source = nil
-
end
-
end
-
rv = super
-
end
-
rv.taint
-
rv
-
end
-
-
1
def read
-
106
begin
-
106
@buffer << readline
-
rescue Exception, NameError
-
@source = nil
-
end
-
end
-
-
1
def consume( pattern )
-
match( pattern, true )
-
end
-
-
1
def match( pattern, cons=false )
-
252
rv = pattern.match(@buffer)
-
252
@buffer = $' if cons and rv
-
252
while !rv and @source
-
begin
-
@buffer << readline
-
rv = pattern.match(@buffer)
-
@buffer = $' if cons and rv
-
rescue
-
@source = nil
-
end
-
end
-
252
rv.taint
-
252
rv
-
end
-
-
1
def empty?
-
220
super and ( @source.nil? || @source.eof? )
-
end
-
-
1
def position
-
@er_source.pos rescue 0
-
end
-
-
# @return the current line in the source
-
1
def current_line
-
64
begin
-
64
pos = @er_source.pos # The byte position in the source
-
64
lineno = @er_source.lineno # The XML < position in the source
-
64
@er_source.rewind
-
64
line = 0 # The \r\n position in the source
-
64
begin
-
64
while @er_source.pos < pos
-
88
@er_source.readline
-
88
line += 1
-
end
-
rescue
-
end
-
rescue IOError
-
pos = -1
-
line = -1
-
end
-
64
[pos, lineno, line]
-
end
-
-
1
private
-
1
def readline
-
106
str = @source.readline(@line_break)
-
106
return nil if str.nil?
-
-
106
if @to_utf
-
decode(str)
-
else
-
106
str.force_encoding(::Encoding::UTF_8) if @force_utf8
-
106
str
-
end
-
end
-
end
-
end
-
1
module REXML
-
1
class SyncEnumerator
-
1
include Enumerable
-
-
# Creates a new SyncEnumerator which enumerates rows of given
-
# Enumerable objects.
-
1
def initialize(*enums)
-
@gens = enums
-
@length = @gens.collect {|x| x.size }.max
-
end
-
-
# Returns the number of enumerated Enumerable objects, i.e. the size
-
# of each row.
-
1
def size
-
@gens.size
-
end
-
-
# Returns the number of enumerated Enumerable objects, i.e. the size
-
# of each row.
-
1
def length
-
@gens.length
-
end
-
-
# Enumerates rows of the Enumerable objects.
-
1
def each
-
@length.times {|i|
-
yield @gens.collect {|x| x[i]}
-
}
-
self
-
end
-
end
-
end
-
1
require 'rexml/entity'
-
1
require 'rexml/doctype'
-
1
require 'rexml/child'
-
1
require 'rexml/doctype'
-
1
require 'rexml/parseexception'
-
-
1
module REXML
-
# Represents text nodes in an XML document
-
1
class Text < Child
-
1
include Comparable
-
# The order in which the substitutions occur
-
1
SPECIALS = [ /&(?!#?[\w-]+;)/u, /</u, />/u, /"/u, /'/u, /\r/u ]
-
1
SUBSTITUTES = ['&', '<', '>', '"', ''', ' ']
-
# Characters which are substituted in written strings
-
1
SLAICEPS = [ '<', '>', '"', "'", '&' ]
-
1
SETUTITSBUS = [ /</u, />/u, /"/u, /'/u, /&/u ]
-
-
# If +raw+ is true, then REXML leaves the value alone
-
1
attr_accessor :raw
-
-
1
NEEDS_A_SECOND_CHECK = /(<|&((#{Entity::NAME});|(#0*((?:\d+)|(?:x[a-fA-F0-9]+)));)?)/um
-
1
NUMERICENTITY = /�*((?:\d+)|(?:x[a-fA-F0-9]+));/
-
1
VALID_CHAR = [
-
0x9, 0xA, 0xD,
-
(0x20..0xD7FF),
-
(0xE000..0xFFFD),
-
(0x10000..0x10FFFF)
-
]
-
-
1
if String.method_defined? :encode
-
1
VALID_XML_CHARS = Regexp.new('^['+
-
VALID_CHAR.map { |item|
-
6
case item
-
when Fixnum
-
3
[item].pack('U').force_encoding('utf-8')
-
when Range
-
3
[item.first, '-'.ord, item.last].pack('UUU').force_encoding('utf-8')
-
end
-
}.join +
-
']*$')
-
else
-
VALID_XML_CHARS = /^(
-
[\x09\x0A\x0D\x20-\x7E] # ASCII
-
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
-
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
-
| [\xE1-\xEC\xEE][\x80-\xBF]{2} # straight 3-byte
-
| \xEF[\x80-\xBE]{2} #
-
| \xEF\xBF[\x80-\xBD] # excluding U+fffe and U+ffff
-
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
-
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
-
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
-
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
-
)*$/nx;
-
end
-
-
# Constructor
-
# +arg+ if a String, the content is set to the String. If a Text,
-
# the object is shallowly cloned.
-
#
-
# +respect_whitespace+ (boolean, false) if true, whitespace is
-
# respected
-
#
-
# +parent+ (nil) if this is a Parent object, the parent
-
# will be set to this.
-
#
-
# +raw+ (nil) This argument can be given three values.
-
# If true, then the value of used to construct this object is expected to
-
# contain no unescaped XML markup, and REXML will not change the text. If
-
# this value is false, the string may contain any characters, and REXML will
-
# escape any and all defined entities whose values are contained in the
-
# text. If this value is nil (the default), then the raw value of the
-
# parent will be used as the raw value for this node. If there is no raw
-
# value for the parent, and no value is supplied, the default is false.
-
# Use this field if you have entities defined for some text, and you don't
-
# want REXML to escape that text in output.
-
# Text.new( "<&", false, nil, false ) #-> "<&"
-
# Text.new( "<&", false, nil, false ) #-> "&lt;&amp;"
-
# Text.new( "<&", false, nil, true ) #-> Parse exception
-
# Text.new( "<&", false, nil, true ) #-> "<&"
-
# # Assume that the entity "s" is defined to be "sean"
-
# # and that the entity "r" is defined to be "russell"
-
# Text.new( "sean russell" ) #-> "&s; &r;"
-
# Text.new( "sean russell", false, nil, true ) #-> "sean russell"
-
#
-
# +entity_filter+ (nil) This can be an array of entities to match in the
-
# supplied text. This argument is only useful if +raw+ is set to false.
-
# Text.new( "sean russell", false, nil, false, ["s"] ) #-> "&s; russell"
-
# Text.new( "sean russell", false, nil, true, ["s"] ) #-> "sean russell"
-
# In the last example, the +entity_filter+ argument is ignored.
-
#
-
# +illegal+ INTERNAL USE ONLY
-
1
def initialize(arg, respect_whitespace=false, parent=nil, raw=nil,
-
entity_filter=nil, illegal=NEEDS_A_SECOND_CHECK )
-
-
74
@raw = false
-
74
@parent = nil
-
-
74
if parent
-
super( parent )
-
@raw = parent.raw
-
end
-
-
74
@raw = raw unless raw.nil?
-
74
@entity_filter = entity_filter
-
74
@normalized = @unnormalized = nil
-
-
74
if arg.kind_of? String
-
74
@string = arg.dup
-
74
@string.squeeze!(" \n\t") unless respect_whitespace
-
elsif arg.kind_of? Text
-
@string = arg.to_s
-
@raw = arg.raw
-
elsif
-
raise "Illegal argument of type #{arg.type} for Text constructor (#{arg})"
-
end
-
-
74
@string.gsub!( /\r\n?/, "\n" )
-
-
74
Text.check(@string, illegal, doctype) if @raw
-
end
-
-
1
def parent= parent
-
74
super(parent)
-
74
Text.check(@string, NEEDS_A_SECOND_CHECK, doctype) if @raw and @parent
-
end
-
-
# check for illegal characters
-
1
def Text.check string, pattern, doctype
-
-
# illegal anywhere
-
187
if string !~ VALID_XML_CHARS
-
if String.method_defined? :encode
-
string.chars.each do |c|
-
case c.ord
-
when *VALID_CHAR
-
else
-
raise "Illegal character #{c.inspect} in raw string \"#{string}\""
-
end
-
end
-
else
-
string.scan(/[\x00-\x7F]|[\x80-\xBF][\xC0-\xF0]*|[\xC0-\xF0]/n) do |c|
-
case c.unpack('U')
-
when *VALID_CHAR
-
else
-
raise "Illegal character #{c.inspect} in raw string \"#{string}\""
-
end
-
end
-
end
-
end
-
-
# context sensitive
-
187
string.scan(pattern) do
-
12
if $1[-1] != ?;
-
raise "Illegal character '#{$1}' in raw string \"#{string}\""
-
elsif $1[0] == ?&
-
12
if $5 and $5[0] == ?#
-
case ($5[1] == ?x ? $5[2..-1].to_i(16) : $5[1..-1].to_i)
-
when *VALID_CHAR
-
else
-
raise "Illegal character '#{$1}' in raw string \"#{string}\""
-
end
-
# FIXME: below can't work but this needs API change.
-
# elsif @parent and $3 and !SUBSTITUTES.include?($1)
-
# if !doctype or !doctype.entities.has_key?($3)
-
# raise "Undeclared entity '#{$1}' in raw string \"#{string}\""
-
# end
-
end
-
end
-
end
-
end
-
-
1
def node_type
-
24
:text
-
end
-
-
1
def empty?
-
@string.size==0
-
end
-
-
-
1
def clone
-
return Text.new(self)
-
end
-
-
-
# Appends text to this text node. The text is appended in the +raw+ mode
-
# of this text node.
-
1
def <<( to_append )
-
@string << to_append.gsub( /\r\n?/, "\n" )
-
end
-
-
-
# +other+ a String or a Text
-
# +returns+ the result of (to_s <=> arg.to_s)
-
1
def <=>( other )
-
to_s() <=> other.to_s
-
end
-
-
1
def doctype
-
184
if @parent
-
110
doc = @parent.document
-
110
doc.doctype if doc
-
end
-
end
-
-
1
REFERENCE = /#{Entity::REFERENCE}/
-
# Returns the string value of this text node. This string is always
-
# escaped, meaning that it is a valid XML text node string, and all
-
# entities that can be escaped, have been inserted. This method respects
-
# the entity filter set in the constructor.
-
#
-
# # Assume that the entity "s" is defined to be "sean", and that the
-
# # entity "r" is defined to be "russell"
-
# t = Text.new( "< & sean russell", false, nil, false, ['s'] )
-
# t.to_s #-> "< & &s; russell"
-
# t = Text.new( "< & &s; russell", false, nil, false )
-
# t.to_s #-> "< & &s; russell"
-
# u = Text.new( "sean russell", false, nil, true )
-
# u.to_s #-> "sean russell"
-
1
def to_s
-
24
return @string if @raw
-
return @normalized if @normalized
-
-
@normalized = Text::normalize( @string, doctype, @entity_filter )
-
end
-
-
1
def inspect
-
@string.inspect
-
end
-
-
# Returns the string value of this text. This is the text without
-
# entities, as it might be used programmatically, or printed to the
-
# console. This ignores the 'raw' attribute setting, and any
-
# entity_filter.
-
#
-
# # Assume that the entity "s" is defined to be "sean", and that the
-
# # entity "r" is defined to be "russell"
-
# t = Text.new( "< & sean russell", false, nil, false, ['s'] )
-
# t.value #-> "< & sean russell"
-
# t = Text.new( "< & &s; russell", false, nil, false )
-
# t.value #-> "< & sean russell"
-
# u = Text.new( "sean russell", false, nil, true )
-
# u.value #-> "sean russell"
-
1
def value
-
72
return @unnormalized if @unnormalized
-
36
@unnormalized = Text::unnormalize( @string, doctype )
-
end
-
-
# Sets the contents of this text node. This expects the text to be
-
# unnormalized. It returns self.
-
#
-
# e = Element.new( "a" )
-
# e.add_text( "foo" ) # <a>foo</a>
-
# e[0].value = "bar" # <a>bar</a>
-
# e[0].value = "<a>" # <a><a></a>
-
1
def value=( val )
-
@string = val.gsub( /\r\n?/, "\n" )
-
@unnormalized = nil
-
@normalized = nil
-
@raw = false
-
end
-
-
1
def wrap(string, width, addnewline=false)
-
# Recursively wrap string at width.
-
return string if string.length <= width
-
place = string.rindex(' ', width) # Position in string with last ' ' before cutoff
-
if addnewline then
-
return "\n" + string[0,place] + "\n" + wrap(string[place+1..-1], width)
-
else
-
return string[0,place] + "\n" + wrap(string[place+1..-1], width)
-
end
-
end
-
-
1
def indent_text(string, level=1, style="\t", indentfirstline=true)
-
return string if level < 0
-
new_string = ''
-
string.each_line { |line|
-
indent_string = style * level
-
new_line = (indent_string + line).sub(/[\s]+$/,'')
-
new_string << new_line
-
}
-
new_string.strip! unless indentfirstline
-
return new_string
-
end
-
-
# == DEPRECATED
-
# See REXML::Formatters
-
#
-
1
def write( writer, indent=-1, transitive=false, ie_hack=false )
-
Kernel.warn("#{self.class.name}.write is deprecated. See REXML::Formatters")
-
formatter = if indent > -1
-
REXML::Formatters::Pretty.new( indent )
-
else
-
REXML::Formatters::Default.new
-
end
-
formatter.write( self, writer )
-
end
-
-
# FIXME
-
# This probably won't work properly
-
1
def xpath
-
path = @parent.xpath
-
path += "/text()"
-
return path
-
end
-
-
# Writes out text, substituting special characters beforehand.
-
# +out+ A String, IO, or any other object supporting <<( String )
-
# +input+ the text to substitute and the write out
-
#
-
# z=utf8.unpack("U*")
-
# ascOut=""
-
# z.each{|r|
-
# if r < 0x100
-
# ascOut.concat(r.chr)
-
# else
-
# ascOut.concat(sprintf("&#x%x;", r))
-
# end
-
# }
-
# puts ascOut
-
1
def write_with_substitution out, input
-
copy = input.clone
-
# Doing it like this rather than in a loop improves the speed
-
copy.gsub!( SPECIALS[0], SUBSTITUTES[0] )
-
copy.gsub!( SPECIALS[1], SUBSTITUTES[1] )
-
copy.gsub!( SPECIALS[2], SUBSTITUTES[2] )
-
copy.gsub!( SPECIALS[3], SUBSTITUTES[3] )
-
copy.gsub!( SPECIALS[4], SUBSTITUTES[4] )
-
copy.gsub!( SPECIALS[5], SUBSTITUTES[5] )
-
out << copy
-
end
-
-
# Reads text, substituting entities
-
1
def Text::read_with_substitution( input, illegal=nil )
-
copy = input.clone
-
-
if copy =~ illegal
-
raise ParseException.new( "malformed text: Illegal character #$& in \"#{copy}\"" )
-
end if illegal
-
-
copy.gsub!( /\r\n?/, "\n" )
-
if copy.include? ?&
-
copy.gsub!( SETUTITSBUS[0], SLAICEPS[0] )
-
copy.gsub!( SETUTITSBUS[1], SLAICEPS[1] )
-
copy.gsub!( SETUTITSBUS[2], SLAICEPS[2] )
-
copy.gsub!( SETUTITSBUS[3], SLAICEPS[3] )
-
copy.gsub!( SETUTITSBUS[4], SLAICEPS[4] )
-
copy.gsub!( /�*((?:\d+)|(?:x[a-f0-9]+));/ ) {
-
m=$1
-
#m='0' if m==''
-
m = "0#{m}" if m[0] == ?x
-
[Integer(m)].pack('U*')
-
}
-
end
-
copy
-
end
-
-
1
EREFERENCE = /&(?!#{Entity::NAME};)/
-
# Escapes all possible entities
-
1
def Text::normalize( input, doctype=nil, entity_filter=nil )
-
copy = input.to_s
-
# Doing it like this rather than in a loop improves the speed
-
#copy = copy.gsub( EREFERENCE, '&' )
-
copy = copy.gsub( "&", "&" )
-
if doctype
-
# Replace all ampersands that aren't part of an entity
-
doctype.entities.each_value do |entity|
-
copy = copy.gsub( entity.value,
-
"&#{entity.name};" ) if entity.value and
-
not( entity_filter and entity_filter.include?(entity) )
-
end
-
else
-
# Replace all ampersands that aren't part of an entity
-
DocType::DEFAULT_ENTITIES.each_value do |entity|
-
copy = copy.gsub(entity.value, "&#{entity.name};" )
-
end
-
end
-
copy
-
end
-
-
# Unescapes all possible entities
-
1
def Text::unnormalize( string, doctype=nil, filter=nil, illegal=nil )
-
69
string.gsub( /\r\n?/, "\n" ).gsub( REFERENCE ) {
-
6
ref = $&
-
6
if ref[1] == ?#
-
if ref[2] == ?x
-
[ref[3...-1].to_i(16)].pack('U*')
-
else
-
[ref[2...-1].to_i].pack('U*')
-
end
-
elsif ref == '&'
-
1
'&'
-
elsif filter and filter.include?( ref[1...-1] )
-
ref
-
elsif doctype
-
doctype.entity( ref[1...-1] ) or ref
-
else
-
5
entity_value = DocType::DEFAULT_ENTITIES[ ref[1...-1] ]
-
5
entity_value ? entity_value.value : ref
-
end
-
}
-
end
-
end
-
end
-
1
require 'rexml/parseexception'
-
1
module REXML
-
1
class UndefinedNamespaceException < ParseException
-
1
def initialize( prefix, source, parser )
-
super( "Undefined prefix #{prefix} found" )
-
end
-
end
-
end
-
1
module REXML
-
1
module Validation
-
1
class ValidationException < RuntimeError
-
1
def initialize msg
-
super
-
end
-
end
-
end
-
end
-
1
require 'rexml/encoding'
-
1
require 'rexml/source'
-
-
1
module REXML
-
# NEEDS DOCUMENTATION
-
1
class XMLDecl < Child
-
1
include Encoding
-
-
1
DEFAULT_VERSION = "1.0";
-
1
DEFAULT_ENCODING = "UTF-8";
-
1
DEFAULT_STANDALONE = "no";
-
1
START = '<\?xml';
-
1
STOP = '\?>';
-
-
1
attr_accessor :version, :standalone
-
1
attr_reader :writeencoding, :writethis
-
-
1
def initialize(version=DEFAULT_VERSION, encoding=nil, standalone=nil)
-
1
@writethis = true
-
1
@writeencoding = !encoding.nil?
-
1
if version.kind_of? XMLDecl
-
super()
-
@version = version.version
-
self.encoding = version.encoding
-
@writeencoding = version.writeencoding
-
@standalone = version.standalone
-
else
-
1
super()
-
1
@version = version
-
1
self.encoding = encoding
-
1
@standalone = standalone
-
end
-
1
@version = DEFAULT_VERSION if @version.nil?
-
end
-
-
1
def clone
-
XMLDecl.new(self)
-
end
-
-
# indent::
-
# Ignored. There must be no whitespace before an XML declaration
-
# transitive::
-
# Ignored
-
# ie_hack::
-
# Ignored
-
1
def write(writer, indent=-1, transitive=false, ie_hack=false)
-
return nil unless @writethis or writer.kind_of? Output
-
writer << START.sub(/\\/u, '')
-
if writer.kind_of? Output
-
writer << " #{content writer.encoding}"
-
else
-
writer << " #{content encoding}"
-
end
-
writer << STOP.sub(/\\/u, '')
-
end
-
-
1
def ==( other )
-
other.kind_of?(XMLDecl) and
-
other.version == @version and
-
other.encoding == self.encoding and
-
other.standalone == @standalone
-
end
-
-
1
def xmldecl version, encoding, standalone
-
@version = version
-
self.encoding = encoding
-
@standalone = standalone
-
end
-
-
1
def node_type
-
:xmldecl
-
end
-
-
1
alias :stand_alone? :standalone
-
1
alias :old_enc= :encoding=
-
-
1
def encoding=( enc )
-
1
if enc.nil?
-
1
self.old_enc = "UTF-8"
-
1
@writeencoding = false
-
else
-
self.old_enc = enc
-
@writeencoding = true
-
end
-
1
self.dowrite
-
end
-
-
# Only use this if you do not want the XML declaration to be written;
-
# this object is ignored by the XML writer. Otherwise, instantiate your
-
# own XMLDecl and add it to the document.
-
#
-
# Note that XML 1.1 documents *must* include an XML declaration
-
1
def XMLDecl.default
-
1
rv = XMLDecl.new( "1.0" )
-
1
rv.nowrite
-
1
rv
-
end
-
-
1
def nowrite
-
1
@writethis = false
-
end
-
-
1
def dowrite
-
1
@writethis = true
-
end
-
-
1
def inspect
-
START.sub(/\\/u, '') + " ... " + STOP.sub(/\\/u, '')
-
end
-
-
1
private
-
1
def content(enc)
-
rv = "version='#@version'"
-
rv << " encoding='#{enc}'" if @writeencoding || enc !~ /utf-8/i
-
rv << " standalone='#@standalone'" if @standalone
-
rv
-
end
-
end
-
end
-
1
module REXML
-
# Defines a number of tokens used for parsing XML. Not for general
-
# consumption.
-
1
module XMLTokens
-
1
NCNAME_STR= '[\w:][\-\w.]*'
-
1
NAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}"
-
-
1
NAMECHAR = '[\-\w\.:]'
-
1
NAME = "([\\w:]#{NAMECHAR}*)"
-
1
NMTOKEN = "(?:#{NAMECHAR})+"
-
1
NMTOKENS = "#{NMTOKEN}(\\s+#{NMTOKEN})*"
-
1
REFERENCE = "(?:&#{NAME};|&#\\d+;|&#x[0-9a-fA-F]+;)"
-
-
#REFERENCE = "(?:#{ENTITYREF}|#{CHARREF})"
-
#ENTITYREF = "&#{NAME};"
-
#CHARREF = "&#\\d+;|&#x[0-9a-fA-F]+;"
-
end
-
end
-
1
require 'rexml/functions'
-
1
require 'rexml/xpath_parser'
-
-
1
module REXML
-
# Wrapper class. Use this class to access the XPath functions.
-
1
class XPath
-
1
include Functions
-
# A base Hash object, supposing to be used when initializing a
-
# default empty namespaces set, but is currently unused.
-
# TODO: either set the namespaces=EMPTY_HASH, or deprecate this.
-
1
EMPTY_HASH = {}
-
-
# Finds and returns the first node that matches the supplied xpath.
-
# element::
-
# The context element
-
# path::
-
# The xpath to search for. If not supplied or nil, returns the first
-
# node matching '*'.
-
# namespaces::
-
# If supplied, a Hash which defines a namespace mapping.
-
# variables::
-
# If supplied, a Hash which maps $variables in the query
-
# to values. This can be used to avoid XPath injection attacks
-
# or to automatically handle escaping string values.
-
#
-
# XPath.first( node )
-
# XPath.first( doc, "//b"} )
-
# XPath.first( node, "a/x:b", { "x"=>"http://doofus" } )
-
# XPath.first( node, '/book/publisher/text()=$publisher', {}, {"publisher"=>"O'Reilly"})
-
1
def XPath::first element, path=nil, namespaces=nil, variables={}
-
raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.nil? or namespaces.kind_of?(Hash)
-
raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of?(Hash)
-
parser = XPathParser.new
-
parser.namespaces = namespaces
-
parser.variables = variables
-
path = "*" unless path
-
element = [element] unless element.kind_of? Array
-
parser.parse(path, element).flatten[0]
-
end
-
-
# Iterates over nodes that match the given path, calling the supplied
-
# block with the match.
-
# element::
-
# The context element
-
# path::
-
# The xpath to search for. If not supplied or nil, defaults to '*'
-
# namespaces::
-
# If supplied, a Hash which defines a namespace mapping
-
# variables::
-
# If supplied, a Hash which maps $variables in the query
-
# to values. This can be used to avoid XPath injection attacks
-
# or to automatically handle escaping string values.
-
#
-
# XPath.each( node ) { |el| ... }
-
# XPath.each( node, '/*[@attr='v']' ) { |el| ... }
-
# XPath.each( node, 'ancestor::x' ) { |el| ... }
-
# XPath.each( node, '/book/publisher/text()=$publisher', {}, {"publisher"=>"O'Reilly"}) \
-
# {|el| ... }
-
1
def XPath::each element, path=nil, namespaces=nil, variables={}, &block
-
17
raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.nil? or namespaces.kind_of?(Hash)
-
17
raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of?(Hash)
-
17
parser = XPathParser.new
-
17
parser.namespaces = namespaces
-
17
parser.variables = variables
-
17
path = "*" unless path
-
17
element = [element] unless element.kind_of? Array
-
17
parser.parse(path, element).each( &block )
-
end
-
-
# Returns an array of nodes matching a given XPath.
-
1
def XPath::match element, path=nil, namespaces=nil, variables={}
-
parser = XPathParser.new
-
parser.namespaces = namespaces
-
parser.variables = variables
-
path = "*" unless path
-
element = [element] unless element.kind_of? Array
-
parser.parse(path,element)
-
end
-
end
-
end
-
1
require 'rexml/namespace'
-
1
require 'rexml/xmltokens'
-
1
require 'rexml/attribute'
-
1
require 'rexml/syncenumerator'
-
1
require 'rexml/parsers/xpathparser'
-
-
1
class Object
-
# provides a unified +clone+ operation, for REXML::XPathParser
-
# to use across multiple Object types
-
1
def dclone
-
clone
-
end
-
end
-
1
class Symbol
-
# provides a unified +clone+ operation, for REXML::XPathParser
-
# to use across multiple Object types
-
1
def dclone ; self ; end
-
end
-
1
class Fixnum
-
# provides a unified +clone+ operation, for REXML::XPathParser
-
# to use across multiple Object types
-
1
def dclone ; self ; end
-
end
-
1
class Float
-
# provides a unified +clone+ operation, for REXML::XPathParser
-
# to use across multiple Object types
-
1
def dclone ; self ; end
-
end
-
1
class Array
-
# provides a unified +clone+ operation, for REXML::XPathParser
-
# to use across multiple Object+ types
-
1
def dclone
-
klone = self.clone
-
klone.clear
-
self.each{|v| klone << v.dclone}
-
klone
-
end
-
end
-
-
1
module REXML
-
# You don't want to use this class. Really. Use XPath, which is a wrapper
-
# for this class. Believe me. You don't want to poke around in here.
-
# There is strange, dark magic at work in this code. Beware. Go back! Go
-
# back while you still can!
-
1
class XPathParser
-
1
include XMLTokens
-
1
LITERAL = /^'([^']*)'|^"([^"]*)"/u
-
-
1
def initialize( )
-
17
@parser = REXML::Parsers::XPathParser.new
-
17
@namespaces = nil
-
17
@variables = {}
-
end
-
-
1
def namespaces=( namespaces={} )
-
17
Functions::namespace_context = namespaces
-
17
@namespaces = namespaces
-
end
-
-
1
def variables=( vars={} )
-
17
Functions::variables = vars
-
17
@variables = vars
-
end
-
-
1
def parse path, nodeset
-
#puts "#"*40
-
17
path_stack = @parser.parse( path )
-
#puts "PARSE: #{path} => #{path_stack.inspect}"
-
#puts "PARSE: nodeset = #{nodeset.inspect}"
-
17
match( path_stack, nodeset )
-
end
-
-
1
def get_first path, nodeset
-
#puts "#"*40
-
path_stack = @parser.parse( path )
-
#puts "PARSE: #{path} => #{path_stack.inspect}"
-
#puts "PARSE: nodeset = #{nodeset.inspect}"
-
first( path_stack, nodeset )
-
end
-
-
1
def predicate path, nodeset
-
path_stack = @parser.parse( path )
-
expr( path_stack, nodeset )
-
end
-
-
1
def []=( variable_name, value )
-
@variables[ variable_name ] = value
-
end
-
-
-
# Performs a depth-first (document order) XPath search, and returns the
-
# first match. This is the fastest, lightest way to return a single result.
-
#
-
# FIXME: This method is incomplete!
-
1
def first( path_stack, node )
-
#puts "#{depth}) Entering match( #{path.inspect}, #{tree.inspect} )"
-
return nil if path.size == 0
-
-
case path[0]
-
when :document
-
# do nothing
-
return first( path[1..-1], node )
-
when :child
-
for c in node.children
-
#puts "#{depth}) CHILD checking #{name(c)}"
-
r = first( path[1..-1], c )
-
#puts "#{depth}) RETURNING #{r.inspect}" if r
-
return r if r
-
end
-
when :qname
-
name = path[2]
-
#puts "#{depth}) QNAME #{name(tree)} == #{name} (path => #{path.size})"
-
if node.name == name
-
#puts "#{depth}) RETURNING #{tree.inspect}" if path.size == 3
-
return node if path.size == 3
-
return first( path[3..-1], node )
-
else
-
return nil
-
end
-
when :descendant_or_self
-
r = first( path[1..-1], node )
-
return r if r
-
for c in node.children
-
r = first( path, c )
-
return r if r
-
end
-
when :node
-
return first( path[1..-1], node )
-
when :any
-
return first( path[1..-1], node )
-
end
-
return nil
-
end
-
-
-
1
def match( path_stack, nodeset )
-
#puts "MATCH: path_stack = #{path_stack.inspect}"
-
#puts "MATCH: nodeset = #{nodeset.inspect}"
-
17
r = expr( path_stack, nodeset )
-
#puts "MAIN EXPR => #{r.inspect}"
-
17
r
-
end
-
-
1
private
-
-
-
# Returns a String namespace for a node, given a prefix
-
# The rules are:
-
#
-
# 1. Use the supplied namespace mapping first.
-
# 2. If no mapping was supplied, use the context node to look up the namespace
-
1
def get_namespace( node, prefix )
-
if @namespaces
-
return @namespaces[prefix] || ''
-
else
-
return node.namespace( prefix ) if node.node_type == :element
-
return ''
-
end
-
end
-
-
-
# Expr takes a stack of path elements and a set of nodes (either a Parent
-
# or an Array and returns an Array of matching nodes
-
1
ALL = [ :attribute, :element, :text, :processing_instruction, :comment ]
-
1
ELEMENTS = [ :element ]
-
1
def expr( path_stack, nodeset, context=nil )
-
#puts "#"*15
-
#puts "In expr with #{path_stack.inspect}"
-
#puts "Returning" if path_stack.length == 0 || nodeset.length == 0
-
17
node_types = ELEMENTS
-
17
return nodeset if path_stack.length == 0 || nodeset.length == 0
-
17
while path_stack.length > 0
-
#puts "#"*5
-
#puts "Path stack = #{path_stack.inspect}"
-
#puts "Nodeset is #{nodeset.inspect}"
-
34
if nodeset.length == 0
-
path_stack.clear
-
return []
-
end
-
34
case (op = path_stack.shift)
-
when :document
-
nodeset = [ nodeset[0].root_node ]
-
#puts ":document, nodeset = #{nodeset.inspect}"
-
-
when :qname
-
#puts "IN QNAME"
-
prefix = path_stack.shift
-
name = path_stack.shift
-
nodeset.delete_if do |node|
-
# FIXME: This DOUBLES the time XPath searches take
-
ns = get_namespace( node, prefix )
-
#puts "NS = #{ns.inspect}"
-
#puts "node.node_type == :element => #{node.node_type == :element}"
-
if node.node_type == :element
-
#puts "node.name == #{name} => #{node.name == name}"
-
if node.name == name
-
#puts "node.namespace == #{ns.inspect} => #{node.namespace == ns}"
-
end
-
end
-
!(node.node_type == :element and
-
node.name == name and
-
node.namespace == ns )
-
end
-
node_types = ELEMENTS
-
-
when :any
-
#puts "ANY 1: nodeset = #{nodeset.inspect}"
-
#puts "ANY 1: node_types = #{node_types.inspect}"
-
74
nodeset.delete_if { |node| !node_types.include?(node.node_type) }
-
#puts "ANY 2: nodeset = #{nodeset.inspect}"
-
-
when :self
-
# This space left intentionally blank
-
-
when :processing_instruction
-
target = path_stack.shift
-
nodeset.delete_if do |node|
-
(node.node_type != :processing_instruction) or
-
( target!='' and ( node.target != target ) )
-
end
-
-
when :text
-
nodeset.delete_if { |node| node.node_type != :text }
-
-
when :comment
-
nodeset.delete_if { |node| node.node_type != :comment }
-
-
when :node
-
# This space left intentionally blank
-
node_types = ALL
-
-
when :child
-
17
new_nodeset = []
-
17
nt = nil
-
17
nodeset.each do |node|
-
17
nt = node.node_type
-
17
new_nodeset += node.children if nt == :element or nt == :document
-
end
-
17
nodeset = new_nodeset
-
17
node_types = ELEMENTS
-
-
when :literal
-
return path_stack.shift
-
-
when :attribute
-
new_nodeset = []
-
case path_stack.shift
-
when :qname
-
prefix = path_stack.shift
-
name = path_stack.shift
-
for element in nodeset
-
if element.node_type == :element
-
#puts "Element name = #{element.name}"
-
#puts "get_namespace( #{element.inspect}, #{prefix} ) = #{get_namespace(element, prefix)}"
-
attrib = element.attribute( name, get_namespace(element, prefix) )
-
#puts "attrib = #{attrib.inspect}"
-
new_nodeset << attrib if attrib
-
end
-
end
-
when :any
-
#puts "ANY"
-
for element in nodeset
-
if element.node_type == :element
-
new_nodeset += element.attributes.to_a
-
end
-
end
-
end
-
nodeset = new_nodeset
-
-
when :parent
-
#puts "PARENT 1: nodeset = #{nodeset}"
-
nodeset = nodeset.collect{|n| n.parent}.compact
-
#nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact)
-
#puts "PARENT 2: nodeset = #{nodeset.inspect}"
-
node_types = ELEMENTS
-
-
when :ancestor
-
new_nodeset = []
-
nodeset.each do |node|
-
while node.parent
-
node = node.parent
-
new_nodeset << node unless new_nodeset.include? node
-
end
-
end
-
nodeset = new_nodeset
-
node_types = ELEMENTS
-
-
when :ancestor_or_self
-
new_nodeset = []
-
nodeset.each do |node|
-
if node.node_type == :element
-
new_nodeset << node
-
while ( node.parent )
-
node = node.parent
-
new_nodeset << node unless new_nodeset.include? node
-
end
-
end
-
end
-
nodeset = new_nodeset
-
node_types = ELEMENTS
-
-
when :predicate
-
new_nodeset = []
-
subcontext = { :size => nodeset.size }
-
pred = path_stack.shift
-
nodeset.each_with_index { |node, index|
-
subcontext[ :node ] = node
-
#puts "PREDICATE SETTING CONTEXT INDEX TO #{index+1}"
-
subcontext[ :index ] = index+1
-
pc = pred.dclone
-
#puts "#{node.hash}) Recursing with #{pred.inspect} and [#{node.inspect}]"
-
result = expr( pc, [node], subcontext )
-
result = result[0] if result.kind_of? Array and result.length == 1
-
#puts "#{node.hash}) Result = #{result.inspect} (#{result.class.name})"
-
if result.kind_of? Numeric
-
#puts "Adding node #{node.inspect}" if result == (index+1)
-
new_nodeset << node if result == (index+1)
-
elsif result.instance_of? Array
-
if result.size > 0 and result.inject(false) {|k,s| s or k}
-
#puts "Adding node #{node.inspect}" if result.size > 0
-
new_nodeset << node if result.size > 0
-
end
-
else
-
#puts "Adding node #{node.inspect}" if result
-
new_nodeset << node if result
-
end
-
}
-
#puts "New nodeset = #{new_nodeset.inspect}"
-
#puts "Path_stack = #{path_stack.inspect}"
-
nodeset = new_nodeset
-
=begin
-
predicate = path_stack.shift
-
ns = nodeset.clone
-
result = expr( predicate, ns )
-
#puts "Result = #{result.inspect} (#{result.class.name})"
-
#puts "nodeset = #{nodeset.inspect}"
-
if result.kind_of? Array
-
nodeset = result.zip(ns).collect{|m,n| n if m}.compact
-
else
-
nodeset = result ? nodeset : []
-
end
-
#puts "Outgoing NS = #{nodeset.inspect}"
-
=end
-
-
when :descendant_or_self
-
rv = descendant_or_self( path_stack, nodeset )
-
path_stack.clear
-
nodeset = rv
-
node_types = ELEMENTS
-
-
when :descendant
-
results = []
-
nt = nil
-
nodeset.each do |node|
-
nt = node.node_type
-
results += expr( path_stack.dclone.unshift( :descendant_or_self ),
-
node.children ) if nt == :element or nt == :document
-
end
-
nodeset = results
-
node_types = ELEMENTS
-
-
when :following_sibling
-
#puts "FOLLOWING_SIBLING 1: nodeset = #{nodeset}"
-
results = []
-
nodeset.each do |node|
-
next if node.parent.nil?
-
all_siblings = node.parent.children
-
current_index = all_siblings.index( node )
-
following_siblings = all_siblings[ current_index+1 .. -1 ]
-
results += expr( path_stack.dclone, following_siblings )
-
end
-
#puts "FOLLOWING_SIBLING 2: nodeset = #{nodeset}"
-
nodeset = results
-
-
when :preceding_sibling
-
results = []
-
nodeset.each do |node|
-
next if node.parent.nil?
-
all_siblings = node.parent.children
-
current_index = all_siblings.index( node )
-
preceding_siblings = all_siblings[ 0, current_index ].reverse
-
results += preceding_siblings
-
end
-
nodeset = results
-
node_types = ELEMENTS
-
-
when :preceding
-
new_nodeset = []
-
nodeset.each do |node|
-
new_nodeset += preceding( node )
-
end
-
#puts "NEW NODESET => #{new_nodeset.inspect}"
-
nodeset = new_nodeset
-
node_types = ELEMENTS
-
-
when :following
-
new_nodeset = []
-
nodeset.each do |node|
-
new_nodeset += following( node )
-
end
-
nodeset = new_nodeset
-
node_types = ELEMENTS
-
-
when :namespace
-
#puts "In :namespace"
-
new_nodeset = []
-
prefix = path_stack.shift
-
nodeset.each do |node|
-
if (node.node_type == :element or node.node_type == :attribute)
-
if @namespaces
-
namespaces = @namespaces
-
elsif (node.node_type == :element)
-
namespaces = node.namespaces
-
else
-
namespaces = node.element.namesapces
-
end
-
#puts "Namespaces = #{namespaces.inspect}"
-
#puts "Prefix = #{prefix.inspect}"
-
#puts "Node.namespace = #{node.namespace}"
-
if (node.namespace == namespaces[prefix])
-
new_nodeset << node
-
end
-
end
-
end
-
nodeset = new_nodeset
-
-
when :variable
-
var_name = path_stack.shift
-
return @variables[ var_name ]
-
-
# :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq
-
# TODO: Special case for :or and :and -- not evaluate the right
-
# operand if the left alone determines result (i.e. is true for
-
# :or and false for :and).
-
when :eq, :neq, :lt, :lteq, :gt, :gteq, :or
-
left = expr( path_stack.shift, nodeset.dup, context )
-
#puts "LEFT => #{left.inspect} (#{left.class.name})"
-
right = expr( path_stack.shift, nodeset.dup, context )
-
#puts "RIGHT => #{right.inspect} (#{right.class.name})"
-
res = equality_relational_compare( left, op, right )
-
#puts "RES => #{res.inspect}"
-
return res
-
-
when :and
-
left = expr( path_stack.shift, nodeset.dup, context )
-
#puts "LEFT => #{left.inspect} (#{left.class.name})"
-
return [] unless left
-
if left.respond_to?(:inject) and !left.inject(false) {|a,b| a | b}
-
return []
-
end
-
right = expr( path_stack.shift, nodeset.dup, context )
-
#puts "RIGHT => #{right.inspect} (#{right.class.name})"
-
res = equality_relational_compare( left, op, right )
-
#puts "RES => #{res.inspect}"
-
return res
-
-
when :div
-
left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
-
right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
-
return (left / right)
-
-
when :mod
-
left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
-
right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
-
return (left % right)
-
-
when :mult
-
left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
-
right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
-
return (left * right)
-
-
when :plus
-
left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
-
right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
-
return (left + right)
-
-
when :minus
-
left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
-
right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
-
return (left - right)
-
-
when :union
-
left = expr( path_stack.shift, nodeset, context )
-
right = expr( path_stack.shift, nodeset, context )
-
return (left | right)
-
-
when :neg
-
res = expr( path_stack, nodeset, context )
-
return -(res.to_f)
-
-
when :not
-
when :function
-
func_name = path_stack.shift.tr('-','_')
-
arguments = path_stack.shift
-
#puts "FUNCTION 0: #{func_name}(#{arguments.collect{|a|a.inspect}.join(', ')})"
-
subcontext = context ? nil : { :size => nodeset.size }
-
-
res = []
-
cont = context
-
nodeset.each_with_index { |n, i|
-
if subcontext
-
subcontext[:node] = n
-
subcontext[:index] = i
-
cont = subcontext
-
end
-
arg_clone = arguments.dclone
-
args = arg_clone.collect { |arg|
-
#puts "FUNCTION 1: Calling expr( #{arg.inspect}, [#{n.inspect}] )"
-
expr( arg, [n], cont )
-
}
-
#puts "FUNCTION 2: #{func_name}(#{args.collect{|a|a.inspect}.join(', ')})"
-
Functions.context = cont
-
res << Functions.send( func_name, *args )
-
#puts "FUNCTION 3: #{res[-1].inspect}"
-
}
-
return res
-
-
end
-
end # while
-
#puts "EXPR returning #{nodeset.inspect}"
-
17
return nodeset
-
end
-
-
-
##########################################################
-
# FIXME
-
# The next two methods are BAD MOJO!
-
# This is my achilles heel. If anybody thinks of a better
-
# way of doing this, be my guest. This really sucks, but
-
# it is a wonder it works at all.
-
# ########################################################
-
-
1
def descendant_or_self( path_stack, nodeset )
-
rs = []
-
#puts "#"*80
-
#puts "PATH_STACK = #{path_stack.inspect}"
-
#puts "NODESET = #{nodeset.collect{|n|n.inspect}.inspect}"
-
d_o_s( path_stack, nodeset, rs )
-
#puts "RS = #{rs.collect{|n|n.inspect}.inspect}"
-
document_order(rs.flatten.compact)
-
#rs.flatten.compact
-
end
-
-
1
def d_o_s( p, ns, r )
-
#puts "IN DOS with #{ns.inspect}; ALREADY HAVE #{r.inspect}"
-
nt = nil
-
ns.each_index do |i|
-
n = ns[i]
-
#puts "P => #{p.inspect}"
-
x = expr( p.dclone, [ n ] )
-
nt = n.node_type
-
d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0
-
r.concat(x) if x.size > 0
-
end
-
end
-
-
-
# Reorders an array of nodes so that they are in document order
-
# It tries to do this efficiently.
-
#
-
# FIXME: I need to get rid of this, but the issue is that most of the XPath
-
# interpreter functions as a filter, which means that we lose context going
-
# in and out of function calls. If I knew what the index of the nodes was,
-
# I wouldn't have to do this. Maybe add a document IDX for each node?
-
# Problems with mutable documents. Or, rewrite everything.
-
1
def document_order( array_of_nodes )
-
new_arry = []
-
array_of_nodes.each { |node|
-
node_idx = []
-
np = node.node_type == :attribute ? node.element : node
-
while np.parent and np.parent.node_type == :element
-
node_idx << np.parent.index( np )
-
np = np.parent
-
end
-
new_arry << [ node_idx.reverse, node ]
-
}
-
#puts "new_arry = #{new_arry.inspect}"
-
new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] }
-
end
-
-
-
1
def recurse( nodeset, &block )
-
for node in nodeset
-
yield node
-
recurse( node, &block ) if node.node_type == :element
-
end
-
end
-
-
-
-
# Builds a nodeset of all of the preceding nodes of the supplied node,
-
# in reverse document order
-
# preceding:: includes every element in the document that precedes this node,
-
# except for ancestors
-
1
def preceding( node )
-
#puts "IN PRECEDING"
-
ancestors = []
-
p = node.parent
-
while p
-
ancestors << p
-
p = p.parent
-
end
-
-
acc = []
-
p = preceding_node_of( node )
-
#puts "P = #{p.inspect}"
-
while p
-
if ancestors.include? p
-
ancestors.delete(p)
-
else
-
acc << p
-
end
-
p = preceding_node_of( p )
-
#puts "P = #{p.inspect}"
-
end
-
acc
-
end
-
-
1
def preceding_node_of( node )
-
#puts "NODE: #{node.inspect}"
-
#puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
-
#puts "PARENT NODE: #{node.parent}"
-
psn = node.previous_sibling_node
-
if psn.nil?
-
if node.parent.nil? or node.parent.class == Document
-
return nil
-
end
-
return node.parent
-
#psn = preceding_node_of( node.parent )
-
end
-
while psn and psn.kind_of? Element and psn.children.size > 0
-
psn = psn.children[-1]
-
end
-
psn
-
end
-
-
1
def following( node )
-
#puts "IN PRECEDING"
-
acc = []
-
p = next_sibling_node( node )
-
#puts "P = #{p.inspect}"
-
while p
-
acc << p
-
p = following_node_of( p )
-
#puts "P = #{p.inspect}"
-
end
-
acc
-
end
-
-
1
def following_node_of( node )
-
#puts "NODE: #{node.inspect}"
-
#puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
-
#puts "PARENT NODE: #{node.parent}"
-
if node.kind_of? Element and node.children.size > 0
-
return node.children[0]
-
end
-
return next_sibling_node(node)
-
end
-
-
1
def next_sibling_node(node)
-
psn = node.next_sibling_node
-
while psn.nil?
-
if node.parent.nil? or node.parent.class == Document
-
return nil
-
end
-
node = node.parent
-
psn = node.next_sibling_node
-
#puts "psn = #{psn.inspect}"
-
end
-
return psn
-
end
-
-
1
def norm b
-
case b
-
when true, false
-
return b
-
when 'true', 'false'
-
return Functions::boolean( b )
-
when /^\d+(\.\d+)?$/
-
return Functions::number( b )
-
else
-
return Functions::string( b )
-
end
-
end
-
-
1
def equality_relational_compare( set1, op, set2 )
-
#puts "EQ_REL_COMP(#{set1.inspect} #{op.inspect} #{set2.inspect})"
-
if set1.kind_of? Array and set2.kind_of? Array
-
#puts "#{set1.size} & #{set2.size}"
-
if set1.size == 1 and set2.size == 1
-
set1 = set1[0]
-
set2 = set2[0]
-
elsif set1.size == 0 or set2.size == 0
-
nd = set1.size==0 ? set2 : set1
-
rv = nd.collect { |il| compare( il, op, nil ) }
-
#puts "RV = #{rv.inspect}"
-
return rv
-
else
-
res = []
-
SyncEnumerator.new( set1, set2 ).each { |i1, i2|
-
#puts "i1 = #{i1.inspect} (#{i1.class.name})"
-
#puts "i2 = #{i2.inspect} (#{i2.class.name})"
-
i1 = norm( i1 )
-
i2 = norm( i2 )
-
res << compare( i1, op, i2 )
-
}
-
return res
-
end
-
end
-
#puts "EQ_REL_COMP: #{set1.inspect} (#{set1.class.name}), #{op}, #{set2.inspect} (#{set2.class.name})"
-
#puts "COMPARING VALUES"
-
# If one is nodeset and other is number, compare number to each item
-
# in nodeset s.t. number op number(string(item))
-
# If one is nodeset and other is string, compare string to each item
-
# in nodeset s.t. string op string(item)
-
# If one is nodeset and other is boolean, compare boolean to each item
-
# in nodeset s.t. boolean op boolean(item)
-
if set1.kind_of? Array or set2.kind_of? Array
-
#puts "ISA ARRAY"
-
if set1.kind_of? Array
-
a = set1
-
b = set2
-
else
-
a = set2
-
b = set1
-
end
-
-
case b
-
when true, false
-
return a.collect {|v| compare( Functions::boolean(v), op, b ) }
-
when Numeric
-
return a.collect {|v| compare( Functions::number(v), op, b )}
-
when /^\d+(\.\d+)?$/
-
b = Functions::number( b )
-
#puts "B = #{b.inspect}"
-
return a.collect {|v| compare( Functions::number(v), op, b )}
-
else
-
#puts "Functions::string( #{b}(#{b.class.name}) ) = #{Functions::string(b)}"
-
b = Functions::string( b )
-
return a.collect { |v| compare( Functions::string(v), op, b ) }
-
end
-
else
-
# If neither is nodeset,
-
# If op is = or !=
-
# If either boolean, convert to boolean
-
# If either number, convert to number
-
# Else, convert to string
-
# Else
-
# Convert both to numbers and compare
-
s1 = set1.to_s
-
s2 = set2.to_s
-
#puts "EQ_REL_COMP: #{set1}=>#{s1}, #{set2}=>#{s2}"
-
if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false'
-
#puts "Functions::boolean(#{set1})=>#{Functions::boolean(set1)}"
-
#puts "Functions::boolean(#{set2})=>#{Functions::boolean(set2)}"
-
set1 = Functions::boolean( set1 )
-
set2 = Functions::boolean( set2 )
-
else
-
if op == :eq or op == :neq
-
if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/
-
set1 = Functions::number( s1 )
-
set2 = Functions::number( s2 )
-
else
-
set1 = Functions::string( set1 )
-
set2 = Functions::string( set2 )
-
end
-
else
-
set1 = Functions::number( set1 )
-
set2 = Functions::number( set2 )
-
end
-
end
-
#puts "EQ_REL_COMP: #{set1} #{op} #{set2}"
-
#puts ">>> #{compare( set1, op, set2 )}"
-
return compare( set1, op, set2 )
-
end
-
return false
-
end
-
-
1
def compare a, op, b
-
#puts "COMPARE #{a.inspect}(#{a.class.name}) #{op} #{b.inspect}(#{b.class.name})"
-
case op
-
when :eq
-
a == b
-
when :neq
-
a != b
-
when :lt
-
a < b
-
when :lteq
-
a <= b
-
when :gt
-
a > b
-
when :gteq
-
a >= b
-
when :and
-
a and b
-
when :or
-
a or b
-
else
-
false
-
end
-
end
-
end
-
end
-
# Copyright (c) 2003, 2004 Jim Weirich, 2009 Eric Hodel
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
1
require 'rubygems'
-
1
begin
-
1
gem 'rake'
-
rescue Gem::LoadError
-
end
-
-
1
require 'rake/packagetask'
-
-
##
-
# Create a package based upon a Gem::Specification. Gem packages, as well as
-
# zip files and tar/gzipped packages can be produced by this task.
-
#
-
# In addition to the Rake targets generated by Rake::PackageTask, a
-
# Gem::PackageTask will also generate the following tasks:
-
#
-
# [<b>"<em>package_dir</em>/<em>name</em>-<em>version</em>.gem"</b>]
-
# Create a RubyGems package with the given name and version.
-
#
-
# Example using a Gem::Specification:
-
#
-
# require 'rubygems'
-
# require 'rubygems/package_task'
-
#
-
# spec = Gem::Specification.new do |s|
-
# s.platform = Gem::Platform::RUBY
-
# s.summary = "Ruby based make-like utility."
-
# s.name = 'rake'
-
# s.version = PKG_VERSION
-
# s.requirements << 'none'
-
# s.require_path = 'lib'
-
# s.autorequire = 'rake'
-
# s.files = PKG_FILES
-
# s.description = <<-EOF
-
# Rake is a Make-like program implemented in Ruby. Tasks
-
# and dependencies are specified in standard Ruby syntax.
-
# EOF
-
# end
-
#
-
# Gem::PackageTask.new(spec) do |pkg|
-
# pkg.need_zip = true
-
# pkg.need_tar = true
-
# end
-
-
1
class Gem::PackageTask < Rake::PackageTask
-
-
##
-
# Ruby Gem::Specification containing the metadata for this package. The
-
# name, version and package_files are automatically determined from the
-
# gemspec and don't need to be explicitly provided.
-
-
1
attr_accessor :gem_spec
-
-
##
-
# Create a Gem Package task library. Automatically define the gem if a
-
# block is given. If no block is supplied, then #define needs to be called
-
# to define the task.
-
-
1
def initialize(gem_spec)
-
1
init gem_spec
-
1
yield self if block_given?
-
1
define if block_given?
-
end
-
-
##
-
# Initialization tasks without the "yield self" or define operations.
-
-
1
def init(gem)
-
1
super gem.full_name, :noversion
-
1
@gem_spec = gem
-
1
@package_files += gem_spec.files if gem_spec.files
-
end
-
-
##
-
# Create the Rake tasks and actions specified by this Gem::PackageTask.
-
# (+define+ is automatically called if a block is given to +new+).
-
-
1
def define
-
1
super
-
-
1
task :package => [:gem]
-
-
1
gem_file = File.basename gem_spec.cache_file
-
1
gem_path = File.join package_dir, gem_file
-
1
gem_dir = File.join package_dir, gem_spec.full_name
-
-
1
desc "Build the gem file #{gem_file}"
-
1
task :gem => [gem_path]
-
-
1
trace = Rake.application.options.trace
-
1
Gem.configuration.verbose = trace
-
-
1
file gem_path => [package_dir, gem_dir] + @gem_spec.files do
-
chdir(gem_dir) do
-
when_writing "Creating #{gem_spec.file_name}" do
-
Gem::Builder.new(gem_spec).build
-
verbose trace do
-
mv gem_file, '..'
-
end
-
end
-
end
-
end
-
end
-
-
end
-
-
# = Secure random number generator interface.
-
#
-
# This library is an interface for secure random number generator which is
-
# suitable for generating session key in HTTP cookies, etc.
-
#
-
# It supports following secure random number generators.
-
#
-
# * openssl
-
# * /dev/urandom
-
# * Win32
-
#
-
# == Example
-
#
-
# # random hexadecimal string.
-
# p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"
-
# p SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559"
-
# p SecureRandom.hex(11) #=> "6aca1b5c58e4863e6b81b8"
-
# p SecureRandom.hex(12) #=> "94b2fff3e7fd9b9c391a2306"
-
# p SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23"
-
# ...
-
#
-
# # random base64 string.
-
# p SecureRandom.base64(10) #=> "EcmTPZwWRAozdA=="
-
# p SecureRandom.base64(10) #=> "9b0nsevdwNuM/w=="
-
# p SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg=="
-
# p SecureRandom.base64(11) #=> "l7XEiFja+8EKEtY="
-
# p SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8"
-
# p SecureRandom.base64(13) #=> "vKLJ0tXBHqQOuIcSIg=="
-
# ...
-
#
-
# # random binary string.
-
# p SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
-
# p SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
-
# ...
-
-
1
begin
-
1
require 'openssl'
-
rescue LoadError
-
end
-
-
1
module SecureRandom
-
# SecureRandom.random_bytes generates a random binary string.
-
#
-
# The argument _n_ specifies the length of the result string.
-
#
-
# If _n_ is not specified, 16 is assumed.
-
# It may be larger in future.
-
#
-
# The result may contain any byte: "\x00" - "\xff".
-
#
-
# p SecureRandom.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
-
# p SecureRandom.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"
-
#
-
# If secure random number generator is not available,
-
# NotImplementedError is raised.
-
1
def self.random_bytes(n=nil)
-
4359
n ||= 16
-
-
4359
if defined? OpenSSL::Random
-
4359
@pid = 0 if !defined?(@pid)
-
4359
pid = $$
-
4359
if @pid != pid
-
1
now = Time.now
-
1
ary = [now.to_i, now.nsec, @pid, pid]
-
1
OpenSSL::Random.seed(ary.to_s)
-
1
@pid = pid
-
end
-
4359
return OpenSSL::Random.random_bytes(n)
-
end
-
-
if !defined?(@has_urandom) || @has_urandom
-
flags = File::RDONLY
-
flags |= File::NONBLOCK if defined? File::NONBLOCK
-
flags |= File::NOCTTY if defined? File::NOCTTY
-
begin
-
File.open("/dev/urandom", flags) {|f|
-
unless f.stat.chardev?
-
raise Errno::ENOENT
-
end
-
@has_urandom = true
-
ret = f.readpartial(n)
-
if ret.length != n
-
raise NotImplementedError, "Unexpected partial read from random device"
-
end
-
return ret
-
}
-
rescue Errno::ENOENT
-
@has_urandom = false
-
end
-
end
-
-
if !defined?(@has_win32)
-
begin
-
require 'Win32API'
-
-
crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L')
-
@crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L')
-
-
hProvStr = " " * 4
-
prov_rsa_full = 1
-
crypt_verifycontext = 0xF0000000
-
-
if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0
-
raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
-
end
-
@hProv, = hProvStr.unpack('L')
-
-
@has_win32 = true
-
rescue LoadError
-
@has_win32 = false
-
end
-
end
-
if @has_win32
-
bytes = " ".force_encoding("ASCII-8BIT") * n
-
if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0
-
raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
-
end
-
return bytes
-
end
-
-
raise NotImplementedError, "No random device"
-
end
-
-
# SecureRandom.hex generates a random hex string.
-
#
-
# The argument _n_ specifies the length of the random length.
-
# The length of the result string is twice of _n_.
-
#
-
# If _n_ is not specified, 16 is assumed.
-
# It may be larger in future.
-
#
-
# The result may contain 0-9 and a-f.
-
#
-
# p SecureRandom.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
-
# p SecureRandom.hex #=> "91dc3bfb4de5b11d029d376634589b61"
-
#
-
# If secure random number generator is not available,
-
# NotImplementedError is raised.
-
1
def self.hex(n=nil)
-
4353
random_bytes(n).unpack("H*")[0]
-
end
-
-
# SecureRandom.base64 generates a random base64 string.
-
#
-
# The argument _n_ specifies the length of the random length.
-
# The length of the result string is about 4/3 of _n_.
-
#
-
# If _n_ is not specified, 16 is assumed.
-
# It may be larger in future.
-
#
-
# The result may contain A-Z, a-z, 0-9, "+", "/" and "=".
-
#
-
# p SecureRandom.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
-
# p SecureRandom.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="
-
#
-
# If secure random number generator is not available,
-
# NotImplementedError is raised.
-
#
-
# See RFC 3548 for the definition of base64.
-
1
def self.base64(n=nil)
-
4
[random_bytes(n)].pack("m*").delete("\n")
-
end
-
-
# SecureRandom.urlsafe_base64 generates a random URL-safe base64 string.
-
#
-
# The argument _n_ specifies the length of the random length.
-
# The length of the result string is about 4/3 of _n_.
-
#
-
# If _n_ is not specified, 16 is assumed.
-
# It may be larger in future.
-
#
-
# The boolean argument _padding_ specifies the padding.
-
# If it is false or nil, padding is not generated.
-
# Otherwise padding is generated.
-
# By default, padding is not generated because "=" may be used as a URL delimiter.
-
#
-
# The result may contain A-Z, a-z, 0-9, "-" and "_".
-
# "=" is also used if _padding_ is true.
-
#
-
# p SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
-
# p SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
-
#
-
# p SecureRandom.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
-
# p SecureRandom.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg=="
-
#
-
# If secure random number generator is not available,
-
# NotImplementedError is raised.
-
#
-
# See RFC 3548 for the definition of URL-safe base64.
-
1
def self.urlsafe_base64(n=nil, padding=false)
-
s = [random_bytes(n)].pack("m*")
-
s.delete!("\n")
-
s.tr!("+/", "-_")
-
s.delete!("=") if !padding
-
s
-
end
-
-
# SecureRandom.random_number generates a random number.
-
#
-
# If a positive integer is given as _n_,
-
# SecureRandom.random_number returns an integer:
-
# 0 <= SecureRandom.random_number(n) < n.
-
#
-
# p SecureRandom.random_number(100) #=> 15
-
# p SecureRandom.random_number(100) #=> 88
-
#
-
# If 0 is given or an argument is not given,
-
# SecureRandom.random_number returns a float:
-
# 0.0 <= SecureRandom.random_number() < 1.0.
-
#
-
# p SecureRandom.random_number #=> 0.596506046187744
-
# p SecureRandom.random_number #=> 0.350621695741409
-
#
-
1
def self.random_number(n=0)
-
if 0 < n
-
hex = n.to_s(16)
-
hex = '0' + hex if (hex.length & 1) == 1
-
bin = [hex].pack("H*")
-
mask = bin[0].ord
-
mask |= mask >> 1
-
mask |= mask >> 2
-
mask |= mask >> 4
-
begin
-
rnd = SecureRandom.random_bytes(bin.length)
-
rnd[0] = (rnd[0].ord & mask).chr
-
end until rnd < bin
-
rnd.unpack("H*")[0].hex
-
else
-
# assumption: Float::MANT_DIG <= 64
-
i64 = SecureRandom.random_bytes(8).unpack("Q")[0]
-
Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG)
-
end
-
end
-
-
# SecureRandom.uuid generates a v4 random UUID (Universally Unique IDentifier).
-
#
-
# p SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
-
# p SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
-
# p SecureRandom.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
-
#
-
# The version 4 UUID is purely random (except the version).
-
# It doesn't contain meaningful information such as MAC address, time, etc.
-
#
-
# See RFC 4122 for details of UUID.
-
#
-
1
def self.uuid
-
2
ary = self.random_bytes(16).unpack("NnnnnN")
-
2
ary[2] = (ary[2] & 0x0fff) | 0x4000
-
2
ary[3] = (ary[3] & 0x3fff) | 0x8000
-
2
"%08x-%04x-%04x-%04x-%04x%08x" % ary
-
end
-
-
# Following code is based on David Garamond's GUID library for Ruby.
-
1
def self.lastWin32ErrorMessage # :nodoc:
-
get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
-
format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L')
-
format_message_ignore_inserts = 0x00000200
-
format_message_from_system = 0x00001000
-
-
code = get_last_error.call
-
msg = "\0" * 1024
-
len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil)
-
msg[0, len].tr("\r", '').chomp
-
end
-
end
-
1
require 'socket.so'
-
-
1
class Addrinfo
-
# creates an Addrinfo object from the arguments.
-
#
-
# The arguments are interpreted as similar to self.
-
#
-
# Addrinfo.tcp("0.0.0.0", 4649).family_addrinfo("www.ruby-lang.org", 80)
-
# #=> #<Addrinfo: 221.186.184.68:80 TCP (www.ruby-lang.org:80)>
-
#
-
# Addrinfo.unix("/tmp/sock").family_addrinfo("/tmp/sock2")
-
# #=> #<Addrinfo: /tmp/sock2 SOCK_STREAM>
-
#
-
1
def family_addrinfo(*args)
-
if args.empty?
-
raise ArgumentError, "no address specified"
-
elsif Addrinfo === args.first
-
raise ArgumentError, "too many arguments" if args.length != 1
-
elsif self.ip?
-
raise ArgumentError, "IP address needs host and port but #{args.length} arguments given" if args.length != 2
-
host, port = args
-
Addrinfo.getaddrinfo(host, port, self.pfamily, self.socktype, self.protocol)[0]
-
elsif self.unix?
-
raise ArgumentError, "UNIX socket needs single path argument but #{args.length} arguments given" if args.length != 1
-
path, = args
-
Addrinfo.unix(path)
-
else
-
raise ArgumentError, "unexpected family"
-
end
-
end
-
-
# creates a new Socket connected to the address of +local_addrinfo+.
-
#
-
# If no arguments are given, the address of the socket is not bound.
-
#
-
# If a block is given the created socket is yielded for each address.
-
#
-
1
def connect_internal(local_addrinfo) # :yields: socket
-
sock = Socket.new(self.pfamily, self.socktype, self.protocol)
-
begin
-
sock.ipv6only! if self.ipv6?
-
sock.bind local_addrinfo if local_addrinfo
-
sock.connect(self)
-
if block_given?
-
yield sock
-
else
-
sock
-
end
-
ensure
-
sock.close if !sock.closed? && (block_given? || $!)
-
end
-
end
-
1
private :connect_internal
-
-
# creates a socket connected to the address of self.
-
#
-
# If one or more arguments given as _local_addr_args_,
-
# it is used as the local address of the socket.
-
# _local_addr_args_ is given for family_addrinfo to obtain actual address.
-
#
-
# If no arguments given, the local address of the socket is not bound.
-
#
-
# If a block is given, it is called with the socket and the value of the block is returned.
-
# The socket is returned otherwise.
-
#
-
# Addrinfo.tcp("www.ruby-lang.org", 80).connect_from("0.0.0.0", 4649) {|s|
-
# s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
-
# puts s.read
-
# }
-
#
-
# # Addrinfo object can be taken for the argument.
-
# Addrinfo.tcp("www.ruby-lang.org", 80).connect_from(Addrinfo.tcp("0.0.0.0", 4649)) {|s|
-
# s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
-
# puts s.read
-
# }
-
#
-
1
def connect_from(*local_addr_args, &block)
-
connect_internal(family_addrinfo(*local_addr_args), &block)
-
end
-
-
# creates a socket connected to the address of self.
-
#
-
# If a block is given, it is called with the socket and the value of the block is returned.
-
# The socket is returned otherwise.
-
#
-
# Addrinfo.tcp("www.ruby-lang.org", 80).connect {|s|
-
# s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
-
# puts s.read
-
# }
-
#
-
1
def connect(&block)
-
connect_internal(nil, &block)
-
end
-
-
# creates a socket connected to _remote_addr_args_ and bound to self.
-
#
-
# If a block is given, it is called with the socket and the value of the block is returned.
-
# The socket is returned otherwise.
-
#
-
# Addrinfo.tcp("0.0.0.0", 4649).connect_to("www.ruby-lang.org", 80) {|s|
-
# s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
-
# puts s.read
-
# }
-
#
-
1
def connect_to(*remote_addr_args, &block)
-
remote_addrinfo = family_addrinfo(*remote_addr_args)
-
remote_addrinfo.send(:connect_internal, self, &block)
-
end
-
-
# creates a socket bound to self.
-
#
-
# If a block is given, it is called with the socket and the value of the block is returned.
-
# The socket is returned otherwise.
-
#
-
# Addrinfo.udp("0.0.0.0", 9981).bind {|s|
-
# s.local_address.connect {|s| s.send "hello", 0 }
-
# p s.recv(10) #=> "hello"
-
# }
-
#
-
1
def bind
-
sock = Socket.new(self.pfamily, self.socktype, self.protocol)
-
begin
-
sock.ipv6only! if self.ipv6?
-
sock.setsockopt(:SOCKET, :REUSEADDR, 1)
-
sock.bind(self)
-
if block_given?
-
yield sock
-
else
-
sock
-
end
-
ensure
-
sock.close if !sock.closed? && (block_given? || $!)
-
end
-
end
-
-
# creates a listening socket bound to self.
-
1
def listen(backlog=5)
-
sock = Socket.new(self.pfamily, self.socktype, self.protocol)
-
begin
-
sock.ipv6only! if self.ipv6?
-
sock.setsockopt(:SOCKET, :REUSEADDR, 1)
-
sock.bind(self)
-
sock.listen(backlog)
-
if block_given?
-
yield sock
-
else
-
sock
-
end
-
ensure
-
sock.close if !sock.closed? && (block_given? || $!)
-
end
-
end
-
-
# iterates over the list of Addrinfo objects obtained by Addrinfo.getaddrinfo.
-
#
-
# Addrinfo.foreach(nil, 80) {|x| p x }
-
# #=> #<Addrinfo: 127.0.0.1:80 TCP (:80)>
-
# # #<Addrinfo: 127.0.0.1:80 UDP (:80)>
-
# # #<Addrinfo: [::1]:80 TCP (:80)>
-
# # #<Addrinfo: [::1]:80 UDP (:80)>
-
#
-
1
def self.foreach(nodename, service, family=nil, socktype=nil, protocol=nil, flags=nil, &block)
-
Addrinfo.getaddrinfo(nodename, service, family, socktype, protocol, flags).each(&block)
-
end
-
end
-
-
1
class BasicSocket < IO
-
# Returns an address of the socket suitable for connect in the local machine.
-
#
-
# This method returns _self_.local_address, except following condition.
-
#
-
# - IPv4 unspecified address (0.0.0.0) is replaced by IPv4 loopback address (127.0.0.1).
-
# - IPv6 unspecified address (::) is replaced by IPv6 loopback address (::1).
-
#
-
# If the local address is not suitable for connect, SocketError is raised.
-
# IPv4 and IPv6 address which port is 0 is not suitable for connect.
-
# Unix domain socket which has no path is not suitable for connect.
-
#
-
# Addrinfo.tcp("0.0.0.0", 0).listen {|serv|
-
# p serv.connect_address #=> #<Addrinfo: 127.0.0.1:53660 TCP>
-
# serv.connect_address.connect {|c|
-
# s, _ = serv.accept
-
# p [c, s] #=> [#<Socket:fd 4>, #<Socket:fd 6>]
-
# }
-
# }
-
#
-
1
def connect_address
-
addr = local_address
-
afamily = addr.afamily
-
if afamily == Socket::AF_INET
-
raise SocketError, "unbound IPv4 socket" if addr.ip_port == 0
-
if addr.ip_address == "0.0.0.0"
-
addr = Addrinfo.new(["AF_INET", addr.ip_port, nil, "127.0.0.1"], addr.pfamily, addr.socktype, addr.protocol)
-
end
-
elsif defined?(Socket::AF_INET6) && afamily == Socket::AF_INET6
-
raise SocketError, "unbound IPv6 socket" if addr.ip_port == 0
-
if addr.ip_address == "::"
-
addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol)
-
elsif addr.ip_address == "0.0.0.0" # MacOS X 10.4 returns "a.b.c.d" for IPv4-mapped IPv6 address.
-
addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol)
-
elsif addr.ip_address == "::ffff:0.0.0.0" # MacOS X 10.6 returns "::ffff:a.b.c.d" for IPv4-mapped IPv6 address.
-
addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol)
-
end
-
elsif defined?(Socket::AF_UNIX) && afamily == Socket::AF_UNIX
-
raise SocketError, "unbound Unix socket" if addr.unix_path == ""
-
end
-
addr
-
end
-
end
-
-
1
class Socket < BasicSocket
-
# enable the socket option IPV6_V6ONLY if IPV6_V6ONLY is available.
-
1
def ipv6only!
-
if defined? Socket::IPV6_V6ONLY
-
self.setsockopt(:IPV6, :V6ONLY, 1)
-
end
-
end
-
-
# creates a new socket object connected to host:port using TCP/IP.
-
#
-
# If local_host:local_port is given,
-
# the socket is bound to it.
-
#
-
# If a block is given, the block is called with the socket.
-
# The value of the block is returned.
-
# The socket is closed when this method returns.
-
#
-
# If no block is given, the socket is returned.
-
#
-
# Socket.tcp("www.ruby-lang.org", 80) {|sock|
-
# sock.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
-
# sock.close_write
-
# puts sock.read
-
# }
-
#
-
1
def self.tcp(host, port, local_host=nil, local_port=nil) # :yield: socket
-
last_error = nil
-
ret = nil
-
-
local_addr_list = nil
-
if local_host != nil || local_port != nil
-
local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil)
-
end
-
-
Addrinfo.foreach(host, port, nil, :STREAM) {|ai|
-
if local_addr_list
-
local_addr = local_addr_list.find {|local_ai| local_ai.afamily == ai.afamily }
-
next if !local_addr
-
else
-
local_addr = nil
-
end
-
begin
-
sock = local_addr ? ai.connect_from(local_addr) : ai.connect
-
rescue SystemCallError
-
last_error = $!
-
next
-
end
-
ret = sock
-
break
-
}
-
if !ret
-
if last_error
-
raise last_error
-
else
-
raise SocketError, "no appropriate local address"
-
end
-
end
-
if block_given?
-
begin
-
yield ret
-
ensure
-
ret.close if !ret.closed?
-
end
-
else
-
ret
-
end
-
end
-
-
# :stopdoc:
-
1
def self.ip_sockets_port0(ai_list, reuseaddr)
-
begin
-
sockets = []
-
port = nil
-
ai_list.each {|ai|
-
begin
-
s = Socket.new(ai.pfamily, ai.socktype, ai.protocol)
-
rescue SystemCallError
-
next
-
end
-
sockets << s
-
s.ipv6only! if ai.ipv6?
-
if reuseaddr
-
s.setsockopt(:SOCKET, :REUSEADDR, 1)
-
end
-
if !port
-
s.bind(ai)
-
port = s.local_address.ip_port
-
else
-
s.bind(ai.family_addrinfo(ai.ip_address, port))
-
end
-
}
-
rescue Errno::EADDRINUSE
-
sockets.each {|s|
-
s.close
-
}
-
retry
-
end
-
sockets
-
ensure
-
sockets.each {|s| s.close if !s.closed? } if $!
-
end
-
1
class << self
-
1
private :ip_sockets_port0
-
end
-
-
1
def self.tcp_server_sockets_port0(host)
-
ai_list = Addrinfo.getaddrinfo(host, 0, nil, :STREAM, nil, Socket::AI_PASSIVE)
-
sockets = ip_sockets_port0(ai_list, true)
-
sockets.each {|s|
-
s.listen(5)
-
}
-
sockets
-
ensure
-
sockets.each {|s| s.close if !s.closed? } if $! && sockets
-
end
-
1
class << self
-
1
private :tcp_server_sockets_port0
-
end
-
# :startdoc:
-
-
# creates TCP/IP server sockets for _host_ and _port_.
-
# _host_ is optional.
-
#
-
# If no block given,
-
# it returns an array of listening sockets.
-
#
-
# If a block is given, the block is called with the sockets.
-
# The value of the block is returned.
-
# The socket is closed when this method returns.
-
#
-
# If _port_ is 0, actual port number is choosen dynamically.
-
# However all sockets in the result has same port number.
-
#
-
# # tcp_server_sockets returns two sockets.
-
# sockets = Socket.tcp_server_sockets(1296)
-
# p sockets #=> [#<Socket:fd 3>, #<Socket:fd 4>]
-
#
-
# # The sockets contains IPv6 and IPv4 sockets.
-
# sockets.each {|s| p s.local_address }
-
# #=> #<Addrinfo: [::]:1296 TCP>
-
# # #<Addrinfo: 0.0.0.0:1296 TCP>
-
#
-
# # IPv6 and IPv4 socket has same port number, 53114, even if it is choosen dynamically.
-
# sockets = Socket.tcp_server_sockets(0)
-
# sockets.each {|s| p s.local_address }
-
# #=> #<Addrinfo: [::]:53114 TCP>
-
# # #<Addrinfo: 0.0.0.0:53114 TCP>
-
#
-
# # The block is called with the sockets.
-
# Socket.tcp_server_sockets(0) {|sockets|
-
# p sockets #=> [#<Socket:fd 3>, #<Socket:fd 4>]
-
# }
-
#
-
1
def self.tcp_server_sockets(host=nil, port)
-
if port == 0
-
sockets = tcp_server_sockets_port0(host)
-
else
-
begin
-
last_error = nil
-
sockets = []
-
Addrinfo.foreach(host, port, nil, :STREAM, nil, Socket::AI_PASSIVE) {|ai|
-
begin
-
s = ai.listen
-
rescue SystemCallError
-
last_error = $!
-
next
-
end
-
sockets << s
-
}
-
if sockets.empty?
-
raise last_error
-
end
-
ensure
-
sockets.each {|s| s.close if !s.closed? } if $!
-
end
-
end
-
if block_given?
-
begin
-
yield sockets
-
ensure
-
sockets.each {|s| s.close if !s.closed? }
-
end
-
else
-
sockets
-
end
-
end
-
-
# yield socket and client address for each a connection accepted via given sockets.
-
#
-
# The arguments are a list of sockets.
-
# The individual argument should be a socket or an array of sockets.
-
#
-
# This method yields the block sequentially.
-
# It means that the next connection is not accepted until the block returns.
-
# So concurrent mechanism, thread for example, should be used to service multiple clients at a time.
-
#
-
1
def self.accept_loop(*sockets) # :yield: socket, client_addrinfo
-
sockets.flatten!(1)
-
if sockets.empty?
-
raise ArgumentError, "no sockets"
-
end
-
loop {
-
readable, _, _ = IO.select(sockets)
-
readable.each {|r|
-
begin
-
sock, addr = r.accept_nonblock
-
rescue IO::WaitReadable
-
next
-
end
-
yield sock, addr
-
}
-
}
-
end
-
-
# creates a TCP/IP server on _port_ and calls the block for each connection accepted.
-
# The block is called with a socket and a client_address as an Addrinfo object.
-
#
-
# If _host_ is specified, it is used with _port_ to determine the server addresses.
-
#
-
# The socket is *not* closed when the block returns.
-
# So application should close it explicitly.
-
#
-
# This method calls the block sequentially.
-
# It means that the next connection is not accepted until the block returns.
-
# So concurrent mechanism, thread for example, should be used to service multiple clients at a time.
-
#
-
# Note that Addrinfo.getaddrinfo is used to determine the server socket addresses.
-
# When Addrinfo.getaddrinfo returns two or more addresses,
-
# IPv4 and IPv6 address for example,
-
# all of them are used.
-
# Socket.tcp_server_loop succeeds if one socket can be used at least.
-
#
-
# # Sequential echo server.
-
# # It services only one client at a time.
-
# Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
-
# begin
-
# IO.copy_stream(sock, sock)
-
# ensure
-
# sock.close
-
# end
-
# }
-
#
-
# # Threaded echo server
-
# # It services multiple clients at a time.
-
# # Note that it may accept connections too much.
-
# Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
-
# Thread.new {
-
# begin
-
# IO.copy_stream(sock, sock)
-
# ensure
-
# sock.close
-
# end
-
# }
-
# }
-
#
-
1
def self.tcp_server_loop(host=nil, port, &b) # :yield: socket, client_addrinfo
-
tcp_server_sockets(host, port) {|sockets|
-
accept_loop(sockets, &b)
-
}
-
end
-
-
# :call-seq:
-
# Socket.udp_server_sockets([host, ] port)
-
#
-
# Creates UDP/IP sockets for a UDP server.
-
#
-
# If no block given, it returns an array of sockets.
-
#
-
# If a block is given, the block is called with the sockets.
-
# The value of the block is returned.
-
# The sockets are closed when this method returns.
-
#
-
# If _port_ is zero, some port is choosen.
-
# But the choosen port is used for the all sockets.
-
#
-
# # UDP/IP echo server
-
# Socket.udp_server_sockets(0) {|sockets|
-
# p sockets.first.local_address.ip_port #=> 32963
-
# Socket.udp_server_loop_on(sockets) {|msg, msg_src|
-
# msg_src.reply msg
-
# }
-
# }
-
#
-
1
def self.udp_server_sockets(host=nil, port)
-
last_error = nil
-
sockets = []
-
-
ipv6_recvpktinfo = nil
-
if defined? Socket::AncillaryData
-
if defined? Socket::IPV6_RECVPKTINFO # RFC 3542
-
ipv6_recvpktinfo = Socket::IPV6_RECVPKTINFO
-
elsif defined? Socket::IPV6_PKTINFO # RFC 2292
-
ipv6_recvpktinfo = Socket::IPV6_PKTINFO
-
end
-
end
-
-
local_addrs = Socket.ip_address_list
-
-
ip_list = []
-
Addrinfo.foreach(host, port, nil, :DGRAM, nil, Socket::AI_PASSIVE) {|ai|
-
if ai.ipv4? && ai.ip_address == "0.0.0.0"
-
local_addrs.each {|a|
-
next if !a.ipv4?
-
ip_list << Addrinfo.new(a.to_sockaddr, :INET, :DGRAM, 0);
-
}
-
elsif ai.ipv6? && ai.ip_address == "::" && !ipv6_recvpktinfo
-
local_addrs.each {|a|
-
next if !a.ipv6?
-
ip_list << Addrinfo.new(a.to_sockaddr, :INET6, :DGRAM, 0);
-
}
-
else
-
ip_list << ai
-
end
-
}
-
-
if port == 0
-
sockets = ip_sockets_port0(ip_list, false)
-
else
-
ip_list.each {|ip|
-
ai = Addrinfo.udp(ip.ip_address, port)
-
begin
-
s = ai.bind
-
rescue SystemCallError
-
last_error = $!
-
next
-
end
-
sockets << s
-
}
-
if sockets.empty?
-
raise last_error
-
end
-
end
-
-
sockets.each {|s|
-
ai = s.local_address
-
if ipv6_recvpktinfo && ai.ipv6? && ai.ip_address == "::"
-
s.setsockopt(:IPV6, ipv6_recvpktinfo, 1)
-
end
-
}
-
-
if block_given?
-
begin
-
yield sockets
-
ensure
-
sockets.each {|s| s.close if !s.closed? } if sockets
-
end
-
else
-
sockets
-
end
-
end
-
-
# :call-seq:
-
# Socket.udp_server_recv(sockets) {|msg, msg_src| ... }
-
#
-
# Receive UDP/IP packets from the given _sockets_.
-
# For each packet received, the block is called.
-
#
-
# The block receives _msg_ and _msg_src_.
-
# _msg_ is a string which is the payload of the received packet.
-
# _msg_src_ is a Socket::UDPSource object which is used for reply.
-
#
-
# Socket.udp_server_loop can be implemented using this method as follows.
-
#
-
# udp_server_sockets(host, port) {|sockets|
-
# loop {
-
# readable, _, _ = IO.select(sockets)
-
# udp_server_recv(readable) {|msg, msg_src| ... }
-
# }
-
# }
-
#
-
1
def self.udp_server_recv(sockets)
-
sockets.each {|r|
-
begin
-
msg, sender_addrinfo, _, *controls = r.recvmsg_nonblock
-
rescue IO::WaitReadable
-
next
-
end
-
ai = r.local_address
-
if ai.ipv6? and pktinfo = controls.find {|c| c.cmsg_is?(:IPV6, :PKTINFO) }
-
ai = Addrinfo.udp(pktinfo.ipv6_pktinfo_addr.ip_address, ai.ip_port)
-
yield msg, UDPSource.new(sender_addrinfo, ai) {|reply_msg|
-
r.sendmsg reply_msg, 0, sender_addrinfo, pktinfo
-
}
-
else
-
yield msg, UDPSource.new(sender_addrinfo, ai) {|reply_msg|
-
r.send reply_msg, 0, sender_addrinfo
-
}
-
end
-
}
-
end
-
-
# :call-seq:
-
# Socket.udp_server_loop_on(sockets) {|msg, msg_src| ... }
-
#
-
# Run UDP/IP server loop on the given sockets.
-
#
-
# The return value of Socket.udp_server_sockets is appropriate for the argument.
-
#
-
# It calls the block for each message received.
-
#
-
1
def self.udp_server_loop_on(sockets, &b) # :yield: msg, msg_src
-
loop {
-
readable, _, _ = IO.select(sockets)
-
udp_server_recv(readable, &b)
-
}
-
end
-
-
# :call-seq:
-
# Socket.udp_server_loop(port) {|msg, msg_src| ... }
-
# Socket.udp_server_loop(host, port) {|msg, msg_src| ... }
-
#
-
# creates a UDP/IP server on _port_ and calls the block for each message arrived.
-
# The block is called with the message and its source information.
-
#
-
# This method allocates sockets internally using _port_.
-
# If _host_ is specified, it is used conjunction with _port_ to determine the server addresses.
-
#
-
# The _msg_ is a string.
-
#
-
# The _msg_src_ is a Socket::UDPSource object.
-
# It is used for reply.
-
#
-
# # UDP/IP echo server.
-
# Socket.udp_server_loop(9261) {|msg, msg_src|
-
# msg_src.reply msg
-
# }
-
#
-
1
def self.udp_server_loop(host=nil, port, &b) # :yield: message, message_source
-
udp_server_sockets(host, port) {|sockets|
-
udp_server_loop_on(sockets, &b)
-
}
-
end
-
-
# UDP/IP address information used by Socket.udp_server_loop.
-
1
class UDPSource
-
# +remote_adress+ is an Addrinfo object.
-
#
-
# +local_adress+ is an Addrinfo object.
-
#
-
# +reply_proc+ is a Proc used to send reply back to the source.
-
1
def initialize(remote_address, local_address, &reply_proc)
-
@remote_address = remote_address
-
@local_address = local_address
-
@reply_proc = reply_proc
-
end
-
-
# Address of the source
-
1
attr_reader :remote_address
-
-
# Local address
-
1
attr_reader :local_address
-
-
1
def inspect # :nodoc:
-
"\#<#{self.class}: #{@remote_address.inspect_sockaddr} to #{@local_address.inspect_sockaddr}>"
-
end
-
-
# Sends the String +msg+ to the source
-
1
def reply(msg)
-
@reply_proc.call msg
-
end
-
end
-
-
# creates a new socket connected to path using UNIX socket socket.
-
#
-
# If a block is given, the block is called with the socket.
-
# The value of the block is returned.
-
# The socket is closed when this method returns.
-
#
-
# If no block is given, the socket is returned.
-
#
-
# # talk to /tmp/sock socket.
-
# Socket.unix("/tmp/sock") {|sock|
-
# t = Thread.new { IO.copy_stream(sock, STDOUT) }
-
# IO.copy_stream(STDIN, sock)
-
# t.join
-
# }
-
#
-
1
def self.unix(path) # :yield: socket
-
addr = Addrinfo.unix(path)
-
sock = addr.connect
-
if block_given?
-
begin
-
yield sock
-
ensure
-
sock.close if !sock.closed?
-
end
-
else
-
sock
-
end
-
end
-
-
# creates a UNIX server socket on _path_
-
#
-
# If no block given, it returns a listening socket.
-
#
-
# If a block is given, it is called with the socket and the block value is returned.
-
# When the block exits, the socket is closed and the socket file is removed.
-
#
-
# socket = Socket.unix_server_socket("/tmp/s")
-
# p socket #=> #<Socket:fd 3>
-
# p socket.local_address #=> #<Addrinfo: /tmp/s SOCK_STREAM>
-
#
-
# Socket.unix_server_socket("/tmp/sock") {|s|
-
# p s #=> #<Socket:fd 3>
-
# p s.local_address #=> # #<Addrinfo: /tmp/sock SOCK_STREAM>
-
# }
-
#
-
1
def self.unix_server_socket(path)
-
begin
-
st = File.lstat(path)
-
rescue Errno::ENOENT
-
end
-
if st && st.socket? && st.owned?
-
File.unlink path
-
end
-
s = Addrinfo.unix(path).listen
-
if block_given?
-
begin
-
yield s
-
ensure
-
s.close if !s.closed?
-
File.unlink path
-
end
-
else
-
s
-
end
-
end
-
-
# creates a UNIX socket server on _path_.
-
# It calls the block for each socket accepted.
-
#
-
# If _host_ is specified, it is used with _port_ to determine the server ports.
-
#
-
# The socket is *not* closed when the block returns.
-
# So application should close it.
-
#
-
# This method deletes the socket file pointed by _path_ at first if
-
# the file is a socket file and it is owned by the user of the application.
-
# This is safe only if the directory of _path_ is not changed by a malicious user.
-
# So don't use /tmp/malicious-users-directory/socket.
-
# Note that /tmp/socket and /tmp/your-private-directory/socket is safe assuming that /tmp has sticky bit.
-
#
-
# # Sequential echo server.
-
# # It services only one client at a time.
-
# Socket.unix_server_loop("/tmp/sock") {|sock, client_addrinfo|
-
# begin
-
# IO.copy_stream(sock, sock)
-
# ensure
-
# sock.close
-
# end
-
# }
-
#
-
1
def self.unix_server_loop(path, &b) # :yield: socket, client_addrinfo
-
unix_server_socket(path) {|serv|
-
accept_loop(serv, &b)
-
}
-
end
-
-
end
-
-
#
-
# tempfile - manipulates temporary files
-
#
-
# $Id: tempfile.rb 33089 2011-08-26 23:54:49Z drbrain $
-
#
-
-
1
require 'delegate'
-
1
require 'tmpdir'
-
1
require 'thread'
-
-
# A utility class for managing temporary files. When you create a Tempfile
-
# object, it will create a temporary file with a unique filename. A Tempfile
-
# objects behaves just like a File object, and you can perform all the usual
-
# file operations on it: reading data, writing data, changing its permissions,
-
# etc. So although this class does not explicitly document all instance methods
-
# supported by File, you can in fact call any File instance method on a
-
# Tempfile object.
-
#
-
# == Synopsis
-
#
-
# require 'tempfile'
-
#
-
# file = Tempfile.new('foo')
-
# file.path # => A unique filename in the OS's temp directory,
-
# # e.g.: "/tmp/foo.24722.0"
-
# # This filename contains 'foo' in its basename.
-
# file.write("hello world")
-
# file.rewind
-
# file.read # => "hello world"
-
# file.close
-
# file.unlink # deletes the temp file
-
#
-
# == Good practices
-
#
-
# === Explicit close
-
#
-
# When a Tempfile object is garbage collected, or when the Ruby interpreter
-
# exits, its associated temporary file is automatically deleted. This means
-
# that's it's unnecessary to explicitly delete a Tempfile after use, though
-
# it's good practice to do so: not explicitly deleting unused Tempfiles can
-
# potentially leave behind large amounts of tempfiles on the filesystem
-
# until they're garbage collected. The existence of these temp files can make
-
# it harder to determine a new Tempfile filename.
-
#
-
# Therefore, one should always call #unlink or close in an ensure block, like
-
# this:
-
#
-
# file = Tempfile.new('foo')
-
# begin
-
# ...do something with file...
-
# ensure
-
# file.close
-
# file.unlink # deletes the temp file
-
# end
-
#
-
# === Unlink after creation
-
#
-
# On POSIX systems, it's possible to unlink a file right after creating it,
-
# and before closing it. This removes the filesystem entry without closing
-
# the file handle, so it ensures that only the processes that already had
-
# the file handle open can access the file's contents. It's strongly
-
# recommended that you do this if you do not want any other processes to
-
# be able to read from or write to the Tempfile, and you do not need to
-
# know the Tempfile's filename either.
-
#
-
# For example, a practical use case for unlink-after-creation would be this:
-
# you need a large byte buffer that's too large to comfortably fit in RAM,
-
# e.g. when you're writing a web server and you want to buffer the client's
-
# file upload data.
-
#
-
# Please refer to #unlink for more information and a code example.
-
#
-
# == Minor notes
-
#
-
# Tempfile's filename picking method is both thread-safe and inter-process-safe:
-
# it guarantees that no other threads or processes will pick the same filename.
-
#
-
# Tempfile itself however may not be entirely thread-safe. If you access the
-
# same Tempfile object from multiple threads then you should protect it with a
-
# mutex.
-
1
class Tempfile < DelegateClass(File)
-
1
MAX_TRY = 10 # :nodoc:
-
1
include Dir::Tmpname
-
-
# call-seq:
-
# new(basename, [tmpdir = Dir.tmpdir], [options])
-
#
-
# Creates a temporary file with permissions 0600 (= only readable and
-
# writable by the owner) and opens it with mode "w+".
-
#
-
# The +basename+ parameter is used to determine the name of the
-
# temporary file. You can either pass a String or an Array with
-
# 2 String elements. In the former form, the temporary file's base
-
# name will begin with the given string. In the latter form,
-
# the temporary file's base name will begin with the array's first
-
# element, and end with the second element. For example:
-
#
-
# file = Tempfile.new('hello')
-
# file.path # => something like: "/tmp/hello2843-8392-92849382--0"
-
#
-
# # Use the Array form to enforce an extension in the filename:
-
# file = Tempfile.new(['hello', '.jpg'])
-
# file.path # => something like: "/tmp/hello2843-8392-92849382--0.jpg"
-
#
-
# The temporary file will be placed in the directory as specified
-
# by the +tmpdir+ parameter. By default, this is +Dir.tmpdir+.
-
# When $SAFE > 0 and the given +tmpdir+ is tainted, it uses
-
# '/tmp' as the temporary directory. Please note that ENV values
-
# are tainted by default, and +Dir.tmpdir+'s return value might
-
# come from environment variables (e.g. <tt>$TMPDIR</tt>).
-
#
-
# file = Tempfile.new('hello', '/home/aisaka')
-
# file.path # => something like: "/home/aisaka/hello2843-8392-92849382--0"
-
#
-
# You can also pass an options hash. Under the hood, Tempfile creates
-
# the temporary file using +File.open+. These options will be passed to
-
# +File.open+. This is mostly useful for specifying encoding
-
# options, e.g.:
-
#
-
# Tempfile.new('hello', '/home/aisaka', :encoding => 'ascii-8bit')
-
#
-
# # You can also omit the 'tmpdir' parameter:
-
# Tempfile.new('hello', :encoding => 'ascii-8bit')
-
#
-
# === Exceptions
-
#
-
# If Tempfile.new cannot find a unique filename within a limited
-
# number of tries, then it will raise an exception.
-
1
def initialize(basename, *rest)
-
3326
@data = []
-
3326
@clean_proc = Remover.new(@data)
-
3326
ObjectSpace.define_finalizer(self, @clean_proc)
-
-
3326
create(basename, *rest) do |tmpname, n, opts|
-
3326
mode = File::RDWR|File::CREAT|File::EXCL
-
3326
perm = 0600
-
3326
if opts
-
mode |= opts.delete(:mode) || 0
-
opts[:perm] = perm
-
perm = nil
-
else
-
3326
opts = perm
-
end
-
3326
self.class.locking(tmpname) do
-
3326
@data[1] = @tmpfile = File.open(tmpname, mode, opts)
-
3326
@data[0] = @tmpname = tmpname
-
end
-
3326
@mode = mode & ~(File::CREAT|File::EXCL)
-
3326
perm or opts.freeze
-
3326
@opts = opts
-
end
-
-
3326
super(@tmpfile)
-
end
-
-
# Opens or reopens the file with mode "r+".
-
1
def open
-
@tmpfile.close if @tmpfile
-
@tmpfile = File.open(@tmpname, @mode, @opts)
-
@data[1] = @tmpfile
-
__setobj__(@tmpfile)
-
end
-
-
1
def _close # :nodoc:
-
3308
@tmpfile.close if @tmpfile
-
3308
@tmpfile = nil
-
3308
@data[1] = nil if @data
-
end
-
1
protected :_close
-
-
# Closes the file. If +unlink_now+ is true, then the file will be unlinked
-
# (deleted) after closing. Of course, you can choose to later call #unlink
-
# if you do not unlink it now.
-
#
-
# If you don't explicitly unlink the temporary file, the removal
-
# will be delayed until the object is finalized.
-
1
def close(unlink_now=false)
-
3308
if unlink_now
-
close!
-
else
-
3308
_close
-
end
-
end
-
-
# Closes and unlinks (deletes) the file. Has the same effect as called
-
# <tt>close(true)</tt>.
-
1
def close!
-
_close
-
unlink
-
ObjectSpace.undefine_finalizer(self)
-
end
-
-
# Unlinks (deletes) the file from the filesystem. One should always unlink
-
# the file after using it, as is explained in the "Explicit close" good
-
# practice section in the Tempfile overview:
-
#
-
# file = Tempfile.new('foo')
-
# begin
-
# ...do something with file...
-
# ensure
-
# file.close
-
# file.unlink # deletes the temp file
-
# end
-
#
-
# === Unlink-before-close
-
#
-
# On POSIX systems it's possible to unlink a file before closing it. This
-
# practice is explained in detail in the Tempfile overview (section
-
# "Unlink after creation"); please refer there for more information.
-
#
-
# However, unlink-before-close may not be supported on non-POSIX operating
-
# systems. Microsoft Windows is the most notable case: unlinking a non-closed
-
# file will result in an error, which this method will silently ignore. If
-
# you want to practice unlink-before-close whenever possible, then you should
-
# write code like this:
-
#
-
# file = Tempfile.new('foo')
-
# file.unlink # On Windows this silently fails.
-
# begin
-
# ... do something with file ...
-
# ensure
-
# file.close! # Closes the file handle. If the file wasn't unlinked
-
# # because #unlink failed, then this method will attempt
-
# # to do so again.
-
# end
-
1
def unlink
-
# keep this order for thread safeness
-
return unless @tmpname
-
begin
-
if File.exist?(@tmpname)
-
File.unlink(@tmpname)
-
end
-
# remove tmpname from remover
-
@data[0] = @data[2] = nil
-
@tmpname = nil
-
rescue Errno::EACCES
-
# may not be able to unlink on Windows; just ignore
-
end
-
end
-
1
alias delete unlink
-
-
# Returns the full path name of the temporary file.
-
# This will be nil if #unlink has been called.
-
1
def path
-
3323
@tmpname
-
end
-
-
# Returns the size of the temporary file. As a side effect, the IO
-
# buffer is flushed before determining the size.
-
1
def size
-
2
if @tmpfile
-
2
@tmpfile.flush
-
2
@tmpfile.stat.size
-
elsif @tmpname
-
File.size(@tmpname)
-
else
-
0
-
end
-
end
-
1
alias length size
-
-
# :stopdoc:
-
1
class Remover
-
1
def initialize(data)
-
3326
@pid = $$
-
3326
@data = data
-
end
-
-
1
def call(*args)
-
3326
if @pid == $$
-
3326
path, tmpfile = *@data
-
-
3326
STDERR.print "removing ", path, "..." if $DEBUG
-
-
3326
tmpfile.close if tmpfile
-
-
# keep this order for thread safeness
-
3326
if path
-
3326
File.unlink(path) if File.exist?(path)
-
end
-
-
3326
STDERR.print "done\n" if $DEBUG
-
end
-
end
-
end
-
# :startdoc:
-
-
1
class << self
-
# Creates a new Tempfile.
-
#
-
# If no block is given, this is a synonym for Tempfile.new.
-
#
-
# If a block is given, then a Tempfile object will be constructed,
-
# and the block is run with said object as argument. The Tempfile
-
# oject will be automatically closed after the block terminates.
-
# The call returns the value of the block.
-
#
-
# In any case, all arguments (+*args+) will be passed to Tempfile.new.
-
#
-
# Tempfile.open('foo', '/home/temp') do |f|
-
# ... do something with f ...
-
# end
-
#
-
# # Equivalent:
-
# f = Tempfile.open('foo', '/home/temp')
-
# begin
-
# ... do something with f ...
-
# ensure
-
# f.close
-
# end
-
1
def open(*args)
-
3306
tempfile = new(*args)
-
-
3306
if block_given?
-
3306
begin
-
3306
yield(tempfile)
-
ensure
-
3306
tempfile.close
-
end
-
else
-
tempfile
-
end
-
end
-
-
# :stopdoc:
-
-
# yields with locking for +tmpname+ and returns the result of the
-
# block.
-
1
def locking(tmpname)
-
3326
lock = tmpname + '.lock'
-
3326
mkdir(lock)
-
3326
yield
-
ensure
-
3326
rmdir(lock) if lock
-
end
-
-
1
def mkdir(*args)
-
3326
Dir.mkdir(*args)
-
end
-
-
1
def rmdir(*args)
-
3326
Dir.rmdir(*args)
-
end
-
end
-
end
-
-
1
if __FILE__ == $0
-
# $DEBUG = true
-
f = Tempfile.new("foo")
-
f.print("foo\n")
-
f.close
-
f.open
-
p f.gets # => "foo\n"
-
f.close!
-
end
-
# Timeout long-running blocks
-
#
-
# == Synopsis
-
#
-
# require 'timeout'
-
# status = Timeout::timeout(5) {
-
# # Something that should be interrupted if it takes more than 5 seconds...
-
# }
-
#
-
# == Description
-
#
-
# Timeout provides a way to auto-terminate a potentially long-running
-
# operation if it hasn't finished in a fixed amount of time.
-
#
-
# Previous versions didn't use a module for namespacing, however
-
# #timeout is provided for backwards compatibility. You
-
# should prefer Timeout#timeout instead.
-
#
-
# == Copyright
-
#
-
# Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc.
-
# Copyright:: (C) 2000 Information-technology Promotion Agency, Japan
-
-
1
module Timeout
-
# Raised by Timeout#timeout when the block times out.
-
1
class Error < RuntimeError
-
end
-
1
class ExitException < ::Exception # :nodoc:
-
end
-
-
# :stopdoc:
-
1
THIS_FILE = /\A#{Regexp.quote(__FILE__)}:/o
-
1
CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0
-
# :startdoc:
-
-
# Perform an operation in a block, timing it out if it takes longer
-
# than +sec+ seconds to complete.
-
#
-
# +sec+:: Number of seconds to wait for the block to terminate. Any number
-
# may be used, including Floats to specify fractional seconds.
-
# +klass+:: Exception Class to raise if the block fails to terminate
-
# in +sec+ seconds. Omitting will use the default, Timeout::Error
-
#
-
# The block will be executed on another thread and will be given one
-
# argument: +sec+.
-
#
-
# Returns the result of the block *if* the block completed before
-
# +sec+ seconds, otherwise throws an exception, based on the value of +klass+.
-
#
-
# Note that this is both a method of module Timeout, so you can <tt>include
-
# Timeout</tt> into your classes so they have a #timeout method, as well as
-
# a module method, so you can call it directly as Timeout.timeout().
-
1
def timeout(sec, klass = nil) #:yield: +sec+
-
8
return yield(sec) if sec == nil or sec.zero?
-
8
exception = klass || Class.new(ExitException)
-
8
begin
-
8
begin
-
8
x = Thread.current
-
8
y = Thread.start {
-
8
begin
-
8
sleep sec
-
rescue => e
-
x.raise e
-
else
-
x.raise exception, "execution expired"
-
end
-
}
-
8
return yield(sec)
-
ensure
-
8
if y
-
8
y.kill
-
8
y.join # make sure y is dead.
-
end
-
end
-
rescue exception => e
-
rej = /\A#{Regexp.quote(__FILE__)}:#{__LINE__-4}\z/o
-
(bt = e.backtrace).reject! {|m| rej =~ m}
-
level = -caller(CALLER_OFFSET).size
-
while THIS_FILE =~ bt[level]
-
bt.delete_at(level)
-
level += 1
-
end
-
raise if klass # if exception class is specified, it
-
# would be expected outside.
-
raise Error, e.message, e.backtrace
-
end
-
end
-
-
1
module_function :timeout
-
end
-
-
# Identical to:
-
#
-
# Timeout::timeout(n, e, &block).
-
#
-
# This method is deprecated and provided only for backwards compatibility.
-
# You should use Timeout#timeout instead.
-
1
def timeout(n, e = nil, &block)
-
Timeout::timeout(n, e, &block)
-
end
-
-
# Another name for Timeout::Error, defined for backwards compatibility with
-
# earlier versions of timeout.rb.
-
1
TimeoutError = Timeout::Error
-
#
-
# tmpdir - retrieve temporary directory path
-
#
-
# $Id: tmpdir.rb 31635 2011-05-18 21:19:18Z drbrain $
-
#
-
-
1
require 'fileutils'
-
1
begin
-
1
require 'etc.so'
-
rescue LoadError
-
end
-
-
1
class Dir
-
-
1
@@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp'
-
-
##
-
# Returns the operating system's temporary file path.
-
-
1
def Dir::tmpdir
-
3341
tmp = '.'
-
3341
if $SAFE > 0
-
tmp = @@systmpdir
-
else
-
3341
for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], @@systmpdir, '/tmp']
-
if dir and stat = File.stat(dir) and stat.directory? and stat.writable?
-
3341
tmp = dir
-
3341
break
-
3341
end rescue nil
-
end
-
3341
File.expand_path(tmp)
-
end
-
end
-
-
# Dir.mktmpdir creates a temporary directory.
-
#
-
# The directory is created with 0700 permission.
-
#
-
# The prefix and suffix of the name of the directory is specified by
-
# the optional first argument, <i>prefix_suffix</i>.
-
# - If it is not specified or nil, "d" is used as the prefix and no suffix is used.
-
# - If it is a string, it is used as the prefix and no suffix is used.
-
# - If it is an array, first element is used as the prefix and second element is used as a suffix.
-
#
-
# Dir.mktmpdir {|dir| dir is ".../d..." }
-
# Dir.mktmpdir("foo") {|dir| dir is ".../foo..." }
-
# Dir.mktmpdir(["foo", "bar"]) {|dir| dir is ".../foo...bar" }
-
#
-
# The directory is created under Dir.tmpdir or
-
# the optional second argument <i>tmpdir</i> if non-nil value is given.
-
#
-
# Dir.mktmpdir {|dir| dir is "#{Dir.tmpdir}/d..." }
-
# Dir.mktmpdir(nil, "/var/tmp") {|dir| dir is "/var/tmp/d..." }
-
#
-
# If a block is given,
-
# it is yielded with the path of the directory.
-
# The directory and its contents are removed
-
# using FileUtils.remove_entry_secure before Dir.mktmpdir returns.
-
# The value of the block is returned.
-
#
-
# Dir.mktmpdir {|dir|
-
# # use the directory...
-
# open("#{dir}/foo", "w") { ... }
-
# }
-
#
-
# If a block is not given,
-
# The path of the directory is returned.
-
# In this case, Dir.mktmpdir doesn't remove the directory.
-
#
-
# dir = Dir.mktmpdir
-
# begin
-
# # use the directory...
-
# open("#{dir}/foo", "w") { ... }
-
# ensure
-
# # remove the directory.
-
# FileUtils.remove_entry_secure dir
-
# end
-
#
-
1
def Dir.mktmpdir(prefix_suffix=nil, *rest)
-
32
path = Tmpname.create(prefix_suffix || "d", *rest) {|n| mkdir(n, 0700)}
-
16
if block_given?
-
begin
-
yield path
-
ensure
-
FileUtils.remove_entry_secure path
-
end
-
else
-
16
path
-
end
-
end
-
-
1
module Tmpname # :nodoc:
-
1
module_function
-
-
1
def tmpdir
-
3340
Dir.tmpdir
-
end
-
-
1
def make_tmpname(prefix_suffix, n)
-
3342
case prefix_suffix
-
when String
-
3342
prefix = prefix_suffix
-
3342
suffix = ""
-
when Array
-
prefix = prefix_suffix[0]
-
suffix = prefix_suffix[1]
-
else
-
raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
-
end
-
3342
t = Time.now.strftime("%Y%m%d")
-
3342
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
-
3342
path << "-#{n}" if n
-
3342
path << suffix
-
end
-
-
1
def create(basename, *rest)
-
3342
if opts = Hash.try_convert(rest[-1])
-
opts = opts.dup if rest.pop.equal?(opts)
-
max_try = opts.delete(:max_try)
-
opts = [opts]
-
else
-
3342
opts = []
-
end
-
3342
tmpdir, = *rest
-
3342
if $SAFE > 0 and tmpdir.tainted?
-
tmpdir = '/tmp'
-
else
-
3342
tmpdir ||= tmpdir()
-
end
-
3342
n = nil
-
3342
begin
-
3342
path = File.expand_path(make_tmpname(basename, n), tmpdir)
-
3342
yield(path, n, *opts)
-
rescue Errno::EEXIST
-
n ||= 0
-
n += 1
-
retry if !max_try or n < max_try
-
raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'"
-
end
-
3342
path
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2004-2012 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
1
require 'simplecov'
-
1
SimpleCov.start
-
1
require 'abstract_controller'
-
1
require 'action_view'
-
1
require 'action_mailer/version'
-
-
# Common Active Support usage in Action Mailer
-
1
require 'active_support/rails'
-
1
require 'active_support/core_ext/class'
-
1
require 'active_support/core_ext/module/attr_internal'
-
1
require 'active_support/core_ext/string/inflections'
-
1
require 'active_support/lazy_load_hooks'
-
-
1
module ActionMailer
-
1
extend ::ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :Collector
-
end
-
-
1
autoload :Base
-
1
autoload :DeliveryMethods
-
1
autoload :MailHelper
-
1
autoload :TestCase
-
1
autoload :TestHelper
-
end
-
1
require 'mail'
-
1
require 'action_mailer/queued_message'
-
1
require 'action_mailer/collector'
-
1
require 'active_support/core_ext/string/inflections'
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/queueing'
-
1
require 'action_mailer/log_subscriber'
-
-
1
module ActionMailer
-
# Action Mailer allows you to send email from your application using a mailer model and views.
-
#
-
# = Mailer Models
-
#
-
# To use Action Mailer, you need to create a mailer model.
-
#
-
# $ rails generate mailer Notifier
-
#
-
# The generated model inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
-
# used to generate an email message. In these methods, you can setup variables to be used in
-
# the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments.
-
#
-
# Examples:
-
#
-
# class Notifier < ActionMailer::Base
-
# default from: 'no-reply@example.com',
-
# return_path: 'system@example.com'
-
#
-
# def welcome(recipient)
-
# @account = recipient
-
# mail(to: recipient.email_address_with_name,
-
# bcc: ["bcc@example.com", "Order Watcher <watcher@example.com>"])
-
# end
-
# end
-
#
-
# Within the mailer method, you have access to the following methods:
-
#
-
# * <tt>attachments[]=</tt> - Allows you to add attachments to your email in an intuitive
-
# manner; <tt>attachments['filename.png'] = File.read('path/to/filename.png')</tt>
-
#
-
# * <tt>attachments.inline[]=</tt> - Allows you to add an inline attachment to your email
-
# in the same manner as <tt>attachments[]=</tt>
-
#
-
# * <tt>headers[]=</tt> - Allows you to specify any header field in your email such
-
# as <tt>headers['X-No-Spam'] = 'True'</tt>. Note, while most fields like <tt>To:</tt>
-
# <tt>From:</tt> can only appear once in an email header, other fields like <tt>X-Anything</tt>
-
# can appear multiple times. If you want to change a field that can appear multiple times,
-
# you need to set it to nil first so that Mail knows you are replacing it and not adding
-
# another field of the same name.
-
#
-
# * <tt>headers(hash)</tt> - Allows you to specify multiple headers in your email such
-
# as <tt>headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})</tt>
-
#
-
# * <tt>mail</tt> - Allows you to specify email to be sent.
-
#
-
# The hash passed to the mail method allows you to specify any header that a Mail::Message
-
# will accept (any valid Email header including optional fields).
-
#
-
# The mail method, if not passed a block, will inspect your views and send all the views with
-
# the same name as the method, so the above action would send the +welcome.text.erb+ view
-
# file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email.
-
#
-
# If you want to explicitly render only certain templates, pass a block:
-
#
-
# mail(to: user.email) do |format|
-
# format.text
-
# format.html
-
# end
-
#
-
# The block syntax is also useful in providing information specific to a part:
-
#
-
# mail(to: user.email) do |format|
-
# format.text(:content_transfer_encoding => "base64")
-
# format.html
-
# end
-
#
-
# Or even to render a special view:
-
#
-
# mail(to: user.email) do |format|
-
# format.text
-
# format.html { render "some_other_template" }
-
# end
-
#
-
# = Mailer views
-
#
-
# Like Action Controller, each mailer class has a corresponding view directory in which each
-
# method of the class looks for a template with its name.
-
#
-
# To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same
-
# name as the method in your mailer model. For example, in the mailer defined above, the template at
-
# <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email.
-
#
-
# Variables defined in the model are accessible as instance variables in the view.
-
#
-
# Emails by default are sent in plain text, so a sample view for our model example might look like this:
-
#
-
# Hi <%= @account.name %>,
-
# Thanks for joining our service! Please check back often.
-
#
-
# You can even use Action Pack helpers in these views. For example:
-
#
-
# You got a new note!
-
# <%= truncate(@note.body, length: 25) %>
-
#
-
# If you need to access the subject, from or the recipients in the view, you can do that through message object:
-
#
-
# You got a new note from <%= message.from %>!
-
# <%= truncate(@note.body, length: 25) %>
-
#
-
#
-
# = Generating URLs
-
#
-
# URLs can be generated in mailer views using <tt>url_for</tt> or named routes. Unlike controllers from
-
# Action Pack, the mailer instance doesn't have any context about the incoming request, so you'll need
-
# to provide all of the details needed to generate a URL.
-
#
-
# When using <tt>url_for</tt> you'll need to provide the <tt>:host</tt>, <tt>:controller</tt>, and <tt>:action</tt>:
-
#
-
# <%= url_for(host: "example.com", controller: "welcome", action: "greeting") %>
-
#
-
# When using named routes you only need to supply the <tt>:host</tt>:
-
#
-
# <%= users_url(host: "example.com") %>
-
#
-
# You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the
-
# <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will
-
# have no concept of a current URL from which to determine a relative path.
-
#
-
# It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt>
-
# option as a configuration option in <tt>config/application.rb</tt>:
-
#
-
# config.action_mailer.default_url_options = { :host => "example.com" }
-
#
-
# When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the
-
# <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
-
# will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing
-
# <tt>:only_path => false</tt> will ensure that absolute URLs are generated.
-
#
-
# = Sending mail
-
#
-
# Once a mailer action and template are defined, you can deliver your message or create it and save it
-
# for delivery later:
-
#
-
# Notifier.welcome(david).deliver # sends the email
-
# mail = Notifier.welcome(david) # => a Mail::Message object
-
# mail.deliver # sends the email
-
#
-
# You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
-
#
-
# = Multipart Emails
-
#
-
# Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
-
# multipart templates, where each template is named after the name of the action, followed by the content
-
# type. Each such detected template will be added as a separate part to the message.
-
#
-
# For example, if the following templates exist:
-
# * signup_notification.text.erb
-
# * signup_notification.text.html.erb
-
# * signup_notification.text.xml.builder
-
# * signup_notification.text.yaml.erb
-
#
-
# Each would be rendered and added as a separate part to the message, with the corresponding content
-
# type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
-
# which indicates that the email contains multiple different representations of the same email
-
# body. The same instance variables defined in the action are passed to all email templates.
-
#
-
# Implicit template rendering is not performed if any attachments or parts have been added to the email.
-
# This means that you'll have to manually add each part to the email and set the content type of the email
-
# to <tt>multipart/alternative</tt>.
-
#
-
# = Attachments
-
#
-
# Sending attachment in emails is easy:
-
#
-
# class ApplicationMailer < ActionMailer::Base
-
# def welcome(recipient)
-
# attachments['free_book.pdf'] = File.read('path/to/file.pdf')
-
# mail(to: recipient, subject: "New account information")
-
# end
-
# end
-
#
-
# Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.text.html.erb</tt>
-
# template in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts,
-
# the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside,
-
# and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
-
# with the filename +free_book.pdf+.
-
#
-
# If you need to send attachments with no content, you need to create an empty view for it,
-
# or add an empty body parameter like this:
-
#
-
# class ApplicationMailer < ActionMailer::Base
-
# def welcome(recipient)
-
# attachments['free_book.pdf'] = File.read('path/to/file.pdf')
-
# mail(to: recipient, subject: "New account information", body: "")
-
# end
-
# end
-
#
-
# = Inline Attachments
-
#
-
# You can also specify that a file should be displayed inline with other HTML. This is useful
-
# if you want to display a corporate logo or a photo.
-
#
-
# class ApplicationMailer < ActionMailer::Base
-
# def welcome(recipient)
-
# attachments.inline['photo.png'] = File.read('path/to/photo.png')
-
# mail(to: recipient, subject: "Here is what we look like")
-
# end
-
# end
-
#
-
# And then to reference the image in the view, you create a <tt>welcome.html.erb</tt> file and
-
# make a call to +image_tag+ passing in the attachment you want to display and then call
-
# +url+ on the attachment to get the relative content id path for the image source:
-
#
-
# <h1>Please Don't Cringe</h1>
-
#
-
# <%= image_tag attachments['photo.png'].url -%>
-
#
-
# As we are using Action View's +image_tag+ method, you can pass in any other options you want:
-
#
-
# <h1>Please Don't Cringe</h1>
-
#
-
# <%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%>
-
#
-
# = Observing and Intercepting Mails
-
#
-
# Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to
-
# register classes that are called during the mail delivery life cycle.
-
#
-
# An observer class must implement the <tt>:delivered_email(message)</tt> method which will be
-
# called once for every email sent after the email has been sent.
-
#
-
# An interceptor class must implement the <tt>:delivering_email(message)</tt> method which will be
-
# called before the email is sent, allowing you to make modifications to the email before it hits
-
# the delivery agents. Your class should make any needed modifications directly to the passed
-
# in Mail::Message instance.
-
#
-
# = Default Hash
-
#
-
# Action Mailer provides some intelligent defaults for your emails, these are usually specified in a
-
# default method inside the class definition:
-
#
-
# class Notifier < ActionMailer::Base
-
# default sender: 'system@example.com'
-
# end
-
#
-
# You can pass in any header value that a <tt>Mail::Message</tt> accepts. Out of the box,
-
# <tt>ActionMailer::Base</tt> sets the following:
-
#
-
# * <tt>:mime_version => "1.0"</tt>
-
# * <tt>:charset => "UTF-8",</tt>
-
# * <tt>:content_type => "text/plain",</tt>
-
# * <tt>:parts_order => [ "text/plain", "text/enriched", "text/html" ]</tt>
-
#
-
# <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields,
-
# but Action Mailer translates them appropriately and sets the correct values.
-
#
-
# As you can pass in any header, you need to either quote the header as a string, or pass it in as
-
# an underscored symbol, so the following will work:
-
#
-
# class Notifier < ActionMailer::Base
-
# default 'Content-Transfer-Encoding' => '7bit',
-
# content_description: 'This is a description'
-
# end
-
#
-
# Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you
-
# can define methods that evaluate as the message is being generated:
-
#
-
# class Notifier < ActionMailer::Base
-
# default 'X-Special-Header' => Proc.new { my_method }
-
#
-
# private
-
#
-
# def my_method
-
# 'some complex call'
-
# end
-
# end
-
#
-
# Note that the proc is evaluated right at the start of the mail message generation, so if you
-
# set something in the defaults using a proc, and then set the same thing inside of your
-
# mailer method, it will get over written by the mailer method.
-
#
-
# It is also possible to set these default options that will be used in all mailers through
-
# the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>:
-
#
-
# config.action_mailer.default_options = { from: "no-reply@example.org" }
-
#
-
# = Callbacks
-
#
-
# You can specify callbacks using before_filter and after_filter for configuring your messages.
-
# This may be useful, for example, when you want to add default inline attachments for all
-
# messages sent out by a certain mailer class:
-
#
-
# class Notifier < ActionMailer::Base
-
# before_filter :add_inline_attachment!
-
#
-
# def welcome
-
# mail
-
# end
-
#
-
# private
-
#
-
# def add_inline_attachment!
-
# attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
-
# end
-
# end
-
#
-
# Callbacks in ActionMailer are implemented using AbstractController::Callbacks, so you
-
# can define and configure callbacks in the same manner that you would use callbacks in
-
# classes that inherit from ActionController::Base.
-
#
-
# Note that unless you have a specific reason to do so, you should prefer using before_filter
-
# rather than after_filter in your ActionMailer classes so that headers are parsed properly.
-
#
-
# = Configuration options
-
#
-
# These options are specified on the class level, like
-
# <tt>ActionMailer::Base.raise_delivery_errors = true</tt>
-
#
-
# * <tt>default</tt> - You can pass this in at a class level as well as within the class itself as
-
# per the above section.
-
#
-
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
-
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
-
#
-
# * <tt>smtp_settings</tt> - Allows detailed configuration for <tt>:smtp</tt> delivery method:
-
# * <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default
-
# "localhost" setting.
-
# * <tt>:port</tt> - On the off chance that your mail server doesn't run on port 25, you can change it.
-
# * <tt>:domain</tt> - If you need to specify a HELO domain, you can do it here.
-
# * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.
-
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
-
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the
-
# authentication type here.
-
# This is a symbol and one of <tt>:plain</tt> (will send the password in the clear), <tt>:login</tt> (will
-
# send password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
-
# information and a cryptographic Message Digest 5 algorithm to hash important information)
-
# * <tt>:enable_starttls_auto</tt> - When set to true, detects if STARTTLS is enabled in your SMTP server
-
# and starts to use it.
-
# * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
-
# really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
-
# of an OpenSSL verify constant ('none', 'peer', 'client_once','fail_if_no_peer_cert') or directly the
-
# constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER,...).
-
#
-
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
-
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
-
# * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@address</tt>
-
# added automatically before the message is sent.
-
#
-
# * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method.
-
# * <tt>:location</tt> - The directory into which emails will be written. Defaults to the application
-
# <tt>tmp/mails</tt>.
-
#
-
# * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered.
-
#
-
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default),
-
# <tt>:sendmail</tt>, <tt>:test</tt>, and <tt>:file</tt>. Or you may provide a custom delivery method
-
# object e.g. MyOwnDeliveryMethodClass. See the Mail gem documentation on the interface you need to
-
# implement for a custom delivery agent.
-
#
-
# * <tt>perform_deliveries</tt> - Determines whether emails are actually sent from Action Mailer when you
-
# call <tt>.deliver</tt> on an mail message or on an Action Mailer method. This is on by default but can
-
# be turned off to aid in functional testing.
-
#
-
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
-
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
-
#
-
# * <tt>queue</> - The queue that will be used to deliver the mail. The queue should expect a job that responds to <tt>run</tt>.
-
1
class Base < AbstractController::Base
-
1
include DeliveryMethods
-
1
abstract!
-
-
1
include AbstractController::Logger
-
1
include AbstractController::Rendering
-
1
include AbstractController::Layouts
-
1
include AbstractController::Helpers
-
1
include AbstractController::Translation
-
1
include AbstractController::AssetPaths
-
1
include AbstractController::Callbacks
-
-
1
self.protected_instance_variables = [:@_action_has_layout]
-
-
1
helper ActionMailer::MailHelper
-
-
1
private_class_method :new #:nodoc:
-
-
1
class_attribute :default_params
-
1
self.default_params = {
-
mime_version: "1.0",
-
charset: "UTF-8",
-
content_type: "text/plain",
-
parts_order: [ "text/plain", "text/enriched", "text/html" ]
-
}.freeze
-
-
1
class_attribute :queue
-
1
self.queue = ActiveSupport::SynchronousQueue.new
-
-
1
class << self
-
# Register one or more Observers which will be notified when mail is delivered.
-
1
def register_observers(*observers)
-
observers.flatten.compact.each { |observer| register_observer(observer) }
-
end
-
-
# Register one or more Interceptors which will be called before mail is sent.
-
1
def register_interceptors(*interceptors)
-
interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
-
end
-
-
# Register an Observer which will be notified when mail is delivered.
-
# Either a class or a string can be passed in as the Observer. If a string is passed in
-
# it will be <tt>constantize</tt>d.
-
1
def register_observer(observer)
-
delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
-
Mail.register_observer(delivery_observer)
-
end
-
-
# Register an Interceptor which will be called before mail is sent.
-
# Either a class or a string can be passed in as the Interceptor. If a string is passed in
-
# it will be <tt>constantize</tt>d.
-
1
def register_interceptor(interceptor)
-
delivery_interceptor = (interceptor.is_a?(String) ? interceptor.constantize : interceptor)
-
Mail.register_interceptor(delivery_interceptor)
-
end
-
-
1
def mailer_name
-
6
@mailer_name ||= anonymous? ? "anonymous" : name.underscore
-
end
-
1
attr_writer :mailer_name
-
1
alias :controller_path :mailer_name
-
-
1
def default(value = nil)
-
3
self.default_params = default_params.merge(value).freeze if value
-
3
default_params
-
end
-
# Allows to set defaults through app configuration:
-
#
-
# config.action_mailer.default_options = { from: "no-reply@example.org" }
-
1
alias :default_options= :default
-
-
# Receives a raw email, parses it into an email object, decodes it,
-
# instantiates a new mailer, and passes the email object to the mailer
-
# object's +receive+ method. If you want your mailer to be able to
-
# process incoming messages, you'll need to implement a +receive+
-
# method that accepts the raw email string as a parameter:
-
#
-
# class MyMailer < ActionMailer::Base
-
# def receive(mail)
-
# ...
-
# end
-
# end
-
1
def receive(raw_mail)
-
ActiveSupport::Notifications.instrument("receive.action_mailer") do |payload|
-
mail = Mail.new(raw_mail)
-
set_payload_for_mail(payload, mail)
-
new.receive(mail)
-
end
-
end
-
-
# Wraps an email delivery inside of Active Support Notifications instrumentation. This
-
# method is actually called by the <tt>Mail::Message</tt> object itself through a callback
-
# when you call <tt>:deliver</tt> on the Mail::Message, calling +deliver_mail+ directly
-
# and passing a Mail::Message will do nothing except tell the logger you sent the email.
-
1
def deliver_mail(mail) #:nodoc:
-
2
ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
-
2
set_payload_for_mail(payload, mail)
-
2
yield # Let Mail do the delivery actions
-
end
-
end
-
-
1
def respond_to?(method, include_private = false) #:nodoc:
-
5
super || action_methods.include?(method.to_s)
-
end
-
-
1
protected
-
-
1
def set_payload_for_mail(payload, mail) #:nodoc:
-
2
payload[:mailer] = name
-
2
payload[:message_id] = mail.message_id
-
2
payload[:subject] = mail.subject
-
2
payload[:to] = mail.to
-
2
payload[:from] = mail.from
-
2
payload[:bcc] = mail.bcc if mail.bcc.present?
-
2
payload[:cc] = mail.cc if mail.cc.present?
-
2
payload[:date] = mail.date
-
2
payload[:mail] = mail.encoded
-
end
-
-
1
def method_missing(method_name, *args)
-
2
if action_methods.include?(method_name.to_s)
-
2
QueuedMessage.new(queue, self, method_name, *args)
-
else
-
super
-
end
-
end
-
end
-
-
1
attr_internal :message
-
-
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
-
# will be initialized according to the named method. If not, the mailer will
-
# remain uninitialized (useful when you only need to invoke the "receive"
-
# method, for instance).
-
1
def initialize(method_name=nil, *args)
-
2
super()
-
2
@_message = Mail.new
-
2
process(method_name, *args) if method_name
-
end
-
-
1
def process(*args) #:nodoc:
-
2
lookup_context.skip_default_locale!
-
-
2
generated_mail = super
-
2
unless generated_mail
-
@_message = NullMail.new
-
end
-
end
-
-
1
class NullMail #:nodoc:
-
1
def body; '' end
-
-
1
def method_missing(*args)
-
nil
-
end
-
end
-
-
1
def mailer_name
-
self.class.mailer_name
-
end
-
-
# Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt> object
-
# which will add them to itself.
-
#
-
# headers['X-Special-Domain-Specific-Header'] = "SecretValue"
-
#
-
# You can also pass a hash into headers of header field names and values, which
-
# will then be set on the Mail::Message object:
-
#
-
# headers 'X-Special-Domain-Specific-Header' => "SecretValue",
-
# 'In-Reply-To' => incoming.message_id
-
#
-
# The resulting Mail::Message will have the following in its header:
-
#
-
# X-Special-Domain-Specific-Header: SecretValue
-
1
def headers(args=nil)
-
if args
-
@_message.headers(args)
-
else
-
@_message
-
end
-
end
-
-
# Allows you to add attachments to an email, like so:
-
#
-
# mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
-
#
-
# If you do this, then Mail will take the file name and work out the mime type
-
# set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
-
# base64 encode the contents of the attachment all for you.
-
#
-
# You can also specify overrides if you want by passing a hash instead of a string:
-
#
-
# mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
-
# content: File.read('/path/to/filename.jpg')}
-
#
-
# If you want to use a different encoding than Base64, you can pass an encoding in,
-
# but then it is up to you to pass in the content pre-encoded, and don't expect
-
# Mail to know how to decode this data:
-
#
-
# file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
-
# mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
-
# encoding: 'SpecialEncoding',
-
# content: file_content }
-
#
-
# You can also search for specific attachments:
-
#
-
# # By Filename
-
# mail.attachments['filename.jpg'] # => Mail::Part object or nil
-
#
-
# # or by index
-
# mail.attachments[0] # => Mail::Part (first attachment)
-
#
-
1
def attachments
-
@_message.attachments
-
end
-
-
# The main method that creates the message and renders the email templates. There are
-
# two ways to call this method, with a block, or without a block.
-
#
-
# Both methods accept a headers hash. This hash allows you to specify the most used headers
-
# in an email message, these are:
-
#
-
# * <tt>:subject</tt> - The subject of the message, if this is omitted, Action Mailer will
-
# ask the Rails I18n class for a translated <tt>:subject</tt> in the scope of
-
# <tt>[mailer_scope, action_name]</tt> or if this is missing, will translate the
-
# humanized version of the <tt>action_name</tt>
-
# * <tt>:to</tt> - Who the message is destined for, can be a string of addresses, or an array
-
# of addresses.
-
# * <tt>:from</tt> - Who the message is from
-
# * <tt>:cc</tt> - Who you would like to Carbon-Copy on this email, can be a string of addresses,
-
# or an array of addresses.
-
# * <tt>:bcc</tt> - Who you would like to Blind-Carbon-Copy on this email, can be a string of
-
# addresses, or an array of addresses.
-
# * <tt>:reply_to</tt> - Who to set the Reply-To header of the email to.
-
# * <tt>:date</tt> - The date to say the email was sent on.
-
#
-
# You can set default values for any of the above headers (except :date) by using the <tt>default</tt>
-
# class method:
-
#
-
# class Notifier < ActionMailer::Base
-
# self.default from: 'no-reply@test.lindsaar.net',
-
# bcc: 'email_logger@test.lindsaar.net',
-
# reply_to: 'bounces@test.lindsaar.net'
-
# end
-
#
-
# If you need other headers not listed above, you can either pass them in
-
# as part of the headers hash or use the <tt>headers['name'] = value</tt>
-
# method.
-
#
-
# When a <tt>:return_path</tt> is specified as header, that value will be used as the 'envelope from'
-
# address for the Mail message. Setting this is useful when you want delivery notifications
-
# sent to a different address than the one in <tt>:from</tt>. Mail will actually use the
-
# <tt>:return_path</tt> in preference to the <tt>:sender</tt> in preference to the <tt>:from</tt>
-
# field for the 'envelope from' value.
-
#
-
# If you do not pass a block to the +mail+ method, it will find all templates in the
-
# view paths using by default the mailer name and the method name that it is being
-
# called from, it will then create parts for each of these templates intelligently,
-
# making educated guesses on correct content type and sequence, and return a fully
-
# prepared Mail::Message ready to call <tt>:deliver</tt> on to send.
-
#
-
# For example:
-
#
-
# class Notifier < ActionMailer::Base
-
# default from: 'no-reply@test.lindsaar.net',
-
#
-
# def welcome
-
# mail(to: 'mikel@test.lindsaar.net')
-
# end
-
# end
-
#
-
# Will look for all templates at "app/views/notifier" with name "welcome".
-
# If no welcome template exists, it will raise an ActionView::MissingTemplate error.
-
#
-
# However, those can be customized:
-
#
-
# mail(template_path: 'notifications', template_name: 'another')
-
#
-
# And now it will look for all templates at "app/views/notifications" with name "another".
-
#
-
# If you do pass a block, you can render specific templates of your choice:
-
#
-
# mail(to: 'mikel@test.lindsaar.net') do |format|
-
# format.text
-
# format.html
-
# end
-
#
-
# You can even render text directly without using a template:
-
#
-
# mail(to: 'mikel@test.lindsaar.net') do |format|
-
# format.text { render text: "Hello Mikel!" }
-
# format.html { render text: "<h1>Hello Mikel!</h1>" }
-
# end
-
#
-
# Which will render a <tt>multipart/alternative</tt> email with <tt>text/plain</tt> and
-
# <tt>text/html</tt> parts.
-
#
-
# The block syntax also allows you to customize the part headers if desired:
-
#
-
# mail(:to => 'mikel@test.lindsaar.net') do |format|
-
# format.text(content_transfer_encoding: "base64")
-
# format.html
-
# end
-
#
-
1
def mail(headers={}, &block)
-
2
m = @_message
-
-
# At the beginning, do not consider class default for parts order neither content_type
-
2
content_type = headers[:content_type]
-
2
parts_order = headers[:parts_order]
-
-
# Call all the procs (if any)
-
2
class_default = self.class.default
-
2
default_values = class_default.merge(class_default) do |k,v|
-
8
v.respond_to?(:to_proc) ? instance_eval(&v) : v
-
end
-
-
# Handle defaults
-
2
headers = headers.reverse_merge(default_values)
-
2
headers[:subject] ||= default_i18n_subject
-
-
# Apply charset at the beginning so all fields are properly quoted
-
2
m.charset = charset = headers[:charset]
-
-
# Set configure delivery behavior
-
2
wrap_delivery_behavior!(headers.delete(:delivery_method),headers.delete(:delivery_method_options))
-
-
# Assign all headers except parts_order, content_type and body
-
2
assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
-
12
assignable.each { |k, v| m[k] = v }
-
-
# Render the templates and blocks
-
2
responses, explicit_order = collect_responses_and_parts_order(headers, &block)
-
2
create_parts_from_responses(m, responses)
-
-
# Setup content type, reapply charset and handle parts order
-
2
m.content_type = set_content_type(m, content_type, headers[:content_type])
-
2
m.charset = charset
-
-
2
if m.multipart?
-
1
parts_order ||= explicit_order || headers[:parts_order]
-
1
m.body.set_sort_order(parts_order)
-
1
m.body.sort_parts!
-
end
-
-
2
m
-
end
-
-
1
protected
-
-
1
def set_content_type(m, user_content_type, class_default)
-
2
params = m.content_type_parameters || {}
-
case
-
when user_content_type.present?
-
1
user_content_type
-
when m.has_attachments?
-
if m.attachments.detect { |a| a.inline? }
-
["multipart", "related", params]
-
else
-
["multipart", "mixed", params]
-
end
-
when m.multipart?
-
1
["multipart", "alternative", params]
-
else
-
m.content_type || class_default
-
2
end
-
end
-
-
# Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope.
-
# If it does not find a translation for the +subject+ under the specified scope it will default to a
-
# humanized version of the <tt>action_name</tt>.
-
1
def default_i18n_subject #:nodoc:
-
mailer_scope = self.class.mailer_name.tr('/', '.')
-
I18n.t(:subject, scope: [mailer_scope, action_name], default: action_name.humanize)
-
end
-
-
1
def collect_responses_and_parts_order(headers) #:nodoc:
-
2
responses, parts_order = [], nil
-
-
2
if block_given?
-
1
collector = ActionMailer::Collector.new(lookup_context) { render(action_name) }
-
1
yield(collector)
-
3
parts_order = collector.responses.map { |r| r[:content_type] }
-
1
responses = collector.responses
-
elsif headers[:body]
-
responses << {
-
body: headers.delete(:body),
-
content_type: self.class.default[:content_type] || "text/plain"
-
1
}
-
else
-
templates_path = headers.delete(:template_path) || self.class.mailer_name
-
templates_name = headers.delete(:template_name) || action_name
-
-
each_template(templates_path, templates_name) do |template|
-
self.formats = template.formats
-
-
responses << {
-
body: render(template: template),
-
content_type: template.type.to_s
-
}
-
end
-
end
-
-
2
[responses, parts_order]
-
end
-
-
1
def each_template(paths, name, &block) #:nodoc:
-
templates = lookup_context.find_all(name, Array(paths))
-
if templates.empty?
-
raise ActionView::MissingTemplate.new([paths], name, [paths], false, 'mailer')
-
else
-
templates.uniq { |t| t.formats }.each(&block)
-
end
-
end
-
-
1
def create_parts_from_responses(m, responses) #:nodoc:
-
2
if responses.size == 1 && !m.has_attachments?
-
3
responses[0].each { |k,v| m[k] = v }
-
1
elsif responses.size > 1 && m.has_attachments?
-
container = Mail::Part.new
-
container.content_type = "multipart/alternative"
-
responses.each { |r| insert_part(container, r, m.charset) }
-
m.add_part(container)
-
else
-
3
responses.each { |r| insert_part(m, r, m.charset) }
-
end
-
end
-
-
1
def insert_part(container, response, charset) #:nodoc:
-
2
response[:charset] ||= charset
-
2
part = Mail::Part.new(response)
-
2
container.add_part(part)
-
end
-
-
1
ActiveSupport.run_load_hooks(:action_mailer, self)
-
end
-
end
-
1
require 'abstract_controller/collector'
-
1
require 'active_support/core_ext/hash/reverse_merge'
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
module ActionMailer
-
1
class Collector
-
1
include AbstractController::Collector
-
1
attr_reader :responses
-
-
1
def initialize(context, &block)
-
1
@context = context
-
1
@responses = []
-
1
@default_render = block
-
end
-
-
1
def any(*args, &block)
-
options = args.extract_options!
-
raise ArgumentError, "You have to supply at least one format" if args.empty?
-
args.each { |type| send(type, options.dup, &block) }
-
end
-
1
alias :all :any
-
-
1
def custom(mime, options={})
-
2
options.reverse_merge!(content_type: mime.to_s)
-
2
@context.formats = [mime.to_sym]
-
2
options[:body] = block_given? ? yield : @default_render.call
-
2
@responses << options
-
end
-
end
-
end
-
1
require 'tmpdir'
-
-
1
module ActionMailer
-
# This module handles everything related to mail delivery, from registering
-
# new delivery methods to configuring the mail object to be sent.
-
1
module DeliveryMethods
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :delivery_methods, :delivery_method
-
-
# Do not make this inheritable, because we always want it to propagate
-
1
cattr_accessor :raise_delivery_errors
-
1
self.raise_delivery_errors = true
-
-
1
cattr_accessor :perform_deliveries
-
1
self.perform_deliveries = true
-
-
1
self.delivery_methods = {}.freeze
-
1
self.delivery_method = :smtp
-
-
1
add_delivery_method :smtp, Mail::SMTP,
-
address: "localhost",
-
port: 25,
-
domain: 'localhost.localdomain',
-
user_name: nil,
-
password: nil,
-
authentication: nil,
-
enable_starttls_auto: true
-
-
1
add_delivery_method :file, Mail::FileDelivery,
-
location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
-
-
1
add_delivery_method :sendmail, Mail::Sendmail,
-
location: '/usr/sbin/sendmail',
-
arguments: '-i -t'
-
-
1
add_delivery_method :test, Mail::TestMailer
-
end
-
-
1
module ClassMethods
-
# Provides a list of emails that have been delivered by Mail::TestMailer
-
1
delegate :deliveries, :deliveries=, to: Mail::TestMailer
-
-
# Adds a new delivery method through the given class using the given
-
# symbol as alias and the default options supplied.
-
#
-
# add_delivery_method :sendmail, Mail::Sendmail,
-
# location: '/usr/sbin/sendmail',
-
# arguments: '-i -t'
-
1
def add_delivery_method(symbol, klass, default_options={})
-
4
class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
-
4
send(:"#{symbol}_settings=", default_options)
-
4
self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
-
end
-
-
1
def wrap_delivery_behavior(mail, method=nil, options=nil) # :nodoc:
-
2
method ||= self.delivery_method
-
2
mail.delivery_handler = self
-
-
2
case method
-
when NilClass
-
raise "Delivery method cannot be nil"
-
when Symbol
-
2
if klass = delivery_methods[method]
-
2
mail.delivery_method(klass,(send(:"#{method}_settings") || {}).merge!(options || {}))
-
else
-
raise "Invalid delivery method #{method.inspect}"
-
end
-
else
-
mail.delivery_method(method)
-
end
-
-
2
mail.perform_deliveries = perform_deliveries
-
2
mail.raise_delivery_errors = raise_delivery_errors
-
end
-
end
-
-
1
def wrap_delivery_behavior!(*args) # :nodoc:
-
2
self.class.wrap_delivery_behavior(message, *args)
-
end
-
end
-
end
-
1
module ActionMailer
-
1
class LogSubscriber < ActiveSupport::LogSubscriber
-
1
def deliver(event)
-
return unless logger.info?
-
recipients = Array(event.payload[:to]).join(', ')
-
info("\nSent mail to #{recipients} (#{event.duration.round(1)}ms)")
-
debug(event.payload[:mail])
-
end
-
-
1
def receive(event)
-
return unless logger.info?
-
info("\nReceived mail (#{event.duration.round(1)}ms)")
-
debug(event.payload[:mail])
-
end
-
-
1
def logger
-
4
ActionMailer::Base.logger
-
end
-
end
-
end
-
-
1
ActionMailer::LogSubscriber.attach_to :action_mailer
-
1
module ActionMailer
-
1
module MailHelper
-
# Take the text and format it, indented two spaces for each line, and
-
# wrapped at 72 columns.
-
1
def block_format(text)
-
formatted = text.split(/\n\r?\n/).collect { |paragraph|
-
format_paragraph(paragraph)
-
}.join("\n\n")
-
-
# Make list points stand on their own line
-
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
-
formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
-
-
formatted
-
end
-
-
# Access the mailer instance.
-
1
def mailer
-
@_controller
-
end
-
-
# Access the message instance.
-
1
def message
-
@_message
-
end
-
-
# Access the message attachments list.
-
1
def attachments
-
@_message.attachments
-
end
-
-
# Returns +text+ wrapped at +len+ columns and indented +indent+ spaces.
-
#
-
# my_text = 'Here is a sample text with more than 40 characters'
-
#
-
# format_paragraph(my_text, 25, 4)
-
# # => " Here is a sample text with\n more than 40 characters"
-
1
def format_paragraph(text, len = 72, indent = 2)
-
sentences = [[]]
-
-
text.split.each do |word|
-
if sentences.first.present? && (sentences.last + [word]).join(' ').length > len
-
sentences << [word]
-
else
-
sentences.last << word
-
end
-
end
-
-
sentences.map { |sentence|
-
"#{" " * indent}#{sentence.join(' ')}"
-
}.join "\n"
-
end
-
end
-
end
-
1
require 'delegate'
-
-
1
module ActionMailer
-
1
class QueuedMessage < ::Delegator
-
1
attr_reader :queue
-
-
1
def initialize(queue, mailer_class, method_name, *args)
-
2
@queue = queue
-
2
@job = DeliveryJob.new(mailer_class, method_name, args)
-
end
-
-
1
def __getobj__
-
@job.message
-
end
-
-
# Queues the message for delivery.
-
1
def deliver
-
4
tap { @queue.push @job }
-
end
-
-
1
class DeliveryJob
-
1
def initialize(mailer_class, method_name, args)
-
2
@mailer_class = mailer_class
-
2
@method_name = method_name
-
2
@args = args
-
end
-
-
1
def message
-
2
@message ||= @mailer_class.send(:new, @method_name, *@args).message
-
end
-
-
1
def run
-
2
message.deliver
-
end
-
end
-
end
-
end
-
1
module ActionMailer
-
1
module VERSION #:nodoc:
-
1
MAJOR = 4
-
1
MINOR = 0
-
1
TINY = 0
-
1
PRE = "beta"
-
-
1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
-
end
-
end
-
1
require 'action_pack'
-
1
require 'active_support/rails'
-
1
require 'active_support/core_ext/module/attr_internal'
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/i18n'
-
-
1
module AbstractController
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Base
-
1
autoload :Callbacks
-
1
autoload :Collector
-
1
autoload :Helpers
-
1
autoload :Layouts
-
1
autoload :Logger
-
1
autoload :Rendering
-
1
autoload :Translation
-
1
autoload :AssetPaths
-
1
autoload :ViewPaths
-
1
autoload :UrlFor
-
end
-
1
module AbstractController
-
1
module AssetPaths #:nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
2
config_accessor :asset_host, :assets_dir, :javascripts_dir,
-
:stylesheets_dir, :default_asset_host_protocol, :relative_url_root
-
end
-
end
-
end
-
1
require 'erubis'
-
1
require 'set'
-
1
require 'active_support/configurable'
-
1
require 'active_support/descendants_tracker'
-
1
require 'active_support/core_ext/module/anonymous'
-
-
1
module AbstractController
-
1
class Error < StandardError #:nodoc:
-
end
-
-
1
class ActionNotFound < StandardError #:nodoc:
-
end
-
-
# <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
-
# using it directly, and subclasses (like ActionController::Base) are
-
# expected to provide their own +render+ method, since rendering means
-
# different things depending on the context.
-
1
class Base
-
1
attr_internal :response_body
-
1
attr_internal :action_name
-
1
attr_internal :formats
-
-
1
include ActiveSupport::Configurable
-
1
extend ActiveSupport::DescendantsTracker
-
-
1
undef_method :not_implemented
-
1
class << self
-
1
attr_reader :abstract
-
1
alias_method :abstract?, :abstract
-
-
# Define a controller as abstract. See internal_methods for more
-
# details.
-
1
def abstract!
-
6
@abstract = true
-
end
-
-
1
def inherited(klass) # :nodoc:
-
# define the abstract ivar on subclasses so that we don't get
-
# uninitialized ivar warnings
-
371
unless klass.instance_variable_defined?(:@abstract)
-
349
klass.instance_variable_set(:@abstract, false)
-
end
-
371
super
-
end
-
-
# A list of all internal methods for a controller. This finds the first
-
# abstract superclass of a controller, and gets a list of all public
-
# instance methods on that abstract class. Public instance methods of
-
# a controller would normally be considered action methods, so methods
-
# declared on abstract classes are being removed.
-
# (ActionController::Metal and ActionController::Base are defined as abstract)
-
1
def internal_methods
-
437
controller = self
-
-
437
controller = controller.superclass until controller.abstract?
-
437
controller.public_instance_methods(true)
-
end
-
-
# The list of hidden actions. Defaults to an empty array.
-
# This can be modified by other modules or subclasses
-
# to specify particular actions as hidden.
-
#
-
# ==== Returns
-
# * <tt>Array</tt> - An array of method names that should not be considered actions.
-
1
def hidden_actions
-
56
[]
-
end
-
-
# A list of method names that should be considered actions. This
-
# includes all public instance methods on a controller, less
-
# any internal methods (see #internal_methods), adding back in
-
# any methods that are internal, but still exist on the class
-
# itself. Finally, #hidden_actions are removed.
-
#
-
# ==== Returns
-
# * <tt>Set</tt> - A set of all methods that should be considered actions.
-
1
def action_methods
-
@action_methods ||= begin
-
# All public instance methods of this class, including ancestors
-
437
methods = (public_instance_methods(true) -
-
# Except for public instance methods of Base and its ancestors
-
internal_methods +
-
# Be sure to include shadowed public instance methods of this class
-
4084
public_instance_methods(false)).uniq.map { |x| x.to_s } -
-
# And always exclude explicitly hidden actions
-
hidden_actions.to_a
-
-
# Clear out AS callback method pollution
-
4077
Set.new(methods.reject { |method| method =~ /_one_time_conditions/ })
-
486
end
-
end
-
-
# action_methods are cached and there is sometimes need to refresh
-
# them. clear_action_methods! allows you to do that, so next time
-
# you run action_methods, they will be recalculated
-
1
def clear_action_methods!
-
2360
@action_methods = nil
-
end
-
-
# Returns the full controller name, underscored, without the ending Controller.
-
# For instance, MyApp::MyPostsController would return "my_app/my_posts" for
-
# controller_path.
-
#
-
# ==== Returns
-
# * <tt>String</tt>
-
1
def controller_path
-
2303
@controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
-
end
-
-
# Refresh the cached action_methods when a new action_method is added.
-
1
def method_added(name)
-
2360
super
-
2360
clear_action_methods!
-
end
-
end
-
-
1
abstract!
-
-
# Calls the action going through the entire action dispatch stack.
-
#
-
# The actual method that is called is determined by calling
-
# #method_for_action. If no method can handle the action, then an
-
# ActionNotFound error is raised.
-
#
-
# ==== Returns
-
# * <tt>self</tt>
-
1
def process(action, *args)
-
1626
@_action_name = action_name = action.to_s
-
-
1626
unless action_name = method_for_action(action_name)
-
9
raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}"
-
end
-
-
1617
@_response_body = nil
-
-
1617
process_action(action_name, *args)
-
end
-
-
# Delegates to the class' #controller_path
-
1
def controller_path
-
1563
self.class.controller_path
-
end
-
-
# Delegates to the class' #action_methods
-
1
def action_methods
-
1
self.class.action_methods
-
end
-
-
# Returns true if a method for the action is available and
-
# can be dispatched, false otherwise.
-
#
-
# Notice that <tt>action_methods.include?("foo")</tt> may return
-
# false and <tt>available_action?("foo")</tt> returns true because
-
# this method considers actions that are also available
-
# through other means, for example, implicit render ones.
-
#
-
# ==== Parameters
-
# * <tt>action_name</tt> - The name of an action to be tested
-
#
-
# ==== Returns
-
# * <tt>TrueClass</tt>, <tt>FalseClass</tt>
-
1
def available_action?(action_name)
-
3
method_for_action(action_name).present?
-
end
-
-
1
private
-
-
# Returns true if the name can be considered an action because
-
# it has a method defined in the controller.
-
#
-
# ==== Parameters
-
# * <tt>name</tt> - The name of an action to be tested
-
#
-
# ==== Returns
-
# * <tt>TrueClass</tt>, <tt>FalseClass</tt>
-
#
-
# :api: private
-
1
def action_method?(name)
-
1586
self.class.action_methods.include?(name)
-
end
-
-
# Call the action. Override this in a subclass to modify the
-
# behavior around processing an action. This, and not #process,
-
# is the intended way to override action dispatching.
-
#
-
# Notice that the first argument is the method to be dispatched
-
# which is *not* necessarily the same as the action name.
-
1
def process_action(method_name, *args)
-
1523
send_action(method_name, *args)
-
end
-
-
# Actually call the method associated with the action. Override
-
# this method if you wish to change how action methods are called,
-
# not to add additional behavior around it. For example, you would
-
# override #send_action if you want to inject arguments into the
-
# method.
-
1
alias send_action send
-
-
# If the action name was not found, but a method called "action_missing"
-
# was found, #method_for_action will return "_handle_action_missing".
-
# This method calls #action_missing with the current action name.
-
1
def _handle_action_missing(*args)
-
1
action_missing(@_action_name, *args)
-
end
-
-
# Takes an action name and returns the name of the method that will
-
# handle the action. In normal cases, this method returns the same
-
# name as it receives. By default, if #method_for_action receives
-
# a name that is not an action, it will look for an #action_missing
-
# method and return "_handle_action_missing" if one is found.
-
#
-
# Subclasses may override this method to add additional conditions
-
# that should be considered an action. For instance, an HTTP controller
-
# with a template matching the action name is considered to exist.
-
#
-
# If you override this method to handle additional cases, you may
-
# also provide a method (like _handle_method_missing) to handle
-
# the case.
-
#
-
# If none of these conditions are true, and method_for_action
-
# returns nil, an ActionNotFound exception will be raised.
-
#
-
# ==== Parameters
-
# * <tt>action_name</tt> - An action name to find a method name for
-
#
-
# ==== Returns
-
# * <tt>string</tt> - The name of the method that handles the action
-
# * <tt>nil</tt> - No method name could be found. Raise ActionNotFound.
-
1
def method_for_action(action_name)
-
1586
if action_method?(action_name)
-
1584
action_name
-
2
elsif respond_to?(:action_missing, true)
-
1
"_handle_action_missing"
-
end
-
end
-
end
-
end
-
1
module AbstractController
-
1
module Callbacks
-
1
extend ActiveSupport::Concern
-
-
# Uses ActiveSupport::Callbacks as the base functionality. For
-
# more details on the whole callback system, read the documentation
-
# for ActiveSupport::Callbacks.
-
1
include ActiveSupport::Callbacks
-
-
1
included do
-
4
define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true
-
end
-
-
# Override AbstractController::Base's process_action to run the
-
# process_action callbacks around the normal behavior.
-
1
def process_action(*args)
-
1534
run_callbacks(:process_action) do
-
1442
super
-
end
-
end
-
-
1
module ClassMethods
-
# If :only or :except are used, convert the options into the
-
# :unless and :if options of ActiveSupport::Callbacks.
-
# The basic idea is that :only => :index gets converted to
-
# :if => proc {|c| c.action_name == "index" }.
-
#
-
# ==== Options
-
# * <tt>only</tt> - The callback should be run only for this action
-
# * <tt>except</tt> - The callback should be run for all actions except this action
-
1
def _normalize_callback_options(options)
-
126
_normalize_callback_option(options, :only, :if)
-
126
_normalize_callback_option(options, :except, :unless)
-
end
-
-
1
def _normalize_callback_option(options, from, to) # :nodoc:
-
252
if from = options[from]
-
125
from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ")
-
58
options[to] = Array(options[to]) << from
-
end
-
end
-
-
# Skip before, after, and around filters matching any of the names
-
#
-
# ==== Parameters
-
# * <tt>names</tt> - A list of valid names that could be used for
-
# callbacks. Note that skipping uses Ruby equality, so it's
-
# impossible to skip a callback defined using an anonymous proc
-
# using #skip_filter
-
1
def skip_filter(*names)
-
2
skip_before_filter(*names)
-
2
skip_after_filter(*names)
-
2
skip_around_filter(*names)
-
end
-
-
# Take callback names and an optional callback proc, normalize them,
-
# then call the block with each callback. This allows us to abstract
-
# the normalization across several methods that use it.
-
#
-
# ==== Parameters
-
# * <tt>callbacks</tt> - An array of callbacks, with an optional
-
# options hash as the last parameter.
-
# * <tt>block</tt> - A proc that should be added to the callbacks.
-
#
-
# ==== Block Parameters
-
# * <tt>name</tt> - The callback to be added
-
# * <tt>options</tt> - A hash of options to be used when adding the callback
-
1
def _insert_callbacks(callbacks, block = nil)
-
126
options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
-
126
_normalize_callback_options(options)
-
126
callbacks.push(block) if block
-
126
callbacks.each do |callback|
-
131
yield callback, options
-
end
-
end
-
-
##
-
# :method: before_filter
-
#
-
# :call-seq: before_filter(names, block)
-
#
-
# Append a before filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: prepend_before_filter
-
#
-
# :call-seq: prepend_before_filter(names, block)
-
#
-
# Prepend a before filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: skip_before_filter
-
#
-
# :call-seq: skip_before_filter(names)
-
#
-
# Skip a before filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: append_before_filter
-
#
-
# :call-seq: append_before_filter(names, block)
-
#
-
# Append a before filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: after_filter
-
#
-
# :call-seq: after_filter(names, block)
-
#
-
# Append an after filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: prepend_after_filter
-
#
-
# :call-seq: prepend_after_filter(names, block)
-
#
-
# Prepend an after filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: skip_after_filter
-
#
-
# :call-seq: skip_after_filter(names)
-
#
-
# Skip an after filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: append_after_filter
-
#
-
# :call-seq: append_after_filter(names, block)
-
#
-
# Append an after filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: around_filter
-
#
-
# :call-seq: around_filter(names, block)
-
#
-
# Append an around filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: prepend_around_filter
-
#
-
# :call-seq: prepend_around_filter(names, block)
-
#
-
# Prepend an around filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: skip_around_filter
-
#
-
# :call-seq: skip_around_filter(names)
-
#
-
# Skip an around filter. See _insert_callbacks for parameter details.
-
-
##
-
# :method: append_around_filter
-
#
-
# :call-seq: append_around_filter(names, block)
-
#
-
# Append an around filter. See _insert_callbacks for parameter details.
-
-
# set up before_filter, prepend_before_filter, skip_before_filter, etc.
-
# for each of before, after, and around.
-
1
[:before, :after, :around].each do |filter|
-
3
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
-
# Append a before, after or around filter. See _insert_callbacks
-
# for details on the allowed parameters.
-
def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
-
_insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
-
set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
-
end # end
-
end # end
-
-
# Prepend a before, after or around filter. See _insert_callbacks
-
# for details on the allowed parameters.
-
def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk)
-
_insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
-
set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
-
end # end
-
end # end
-
-
# Skip a before, after or around filter. See _insert_callbacks
-
# for details on the allowed parameters.
-
def skip_#{filter}_filter(*names) # def skip_before_filter(*names)
-
_insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
-
skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
-
end # end
-
end # end
-
-
# *_filter is the same as append_*_filter
-
alias_method :append_#{filter}_filter, :#{filter}_filter # alias_method :append_before_filter, :before_filter
-
RUBY_EVAL
-
end
-
end
-
end
-
end
-
1
require "action_dispatch/http/mime_type"
-
-
1
module AbstractController
-
1
module Collector
-
1
def self.generate_method_for_mime(mime)
-
27
sym = mime.is_a?(Symbol) ? mime : mime.to_sym
-
27
const = sym.upcase
-
27
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{sym}(*args, &block) # def html(*args, &block)
-
custom(Mime::#{const}, *args, &block) # custom(Mime::HTML, *args, &block)
-
end # end
-
RUBY
-
end
-
-
1
Mime::SET.each do |mime|
-
21
generate_method_for_mime(mime)
-
end
-
-
1
Mime::Type.register_callback do |mime|
-
213
generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym)
-
end
-
-
1
protected
-
-
1
def method_missing(symbol, &block)
-
2
mime_constant = Mime.const_get(symbol.upcase)
-
-
1
if Mime::SET.include?(mime_constant)
-
1
AbstractController::Collector.generate_method_for_mime(mime_constant)
-
1
send(symbol, &block)
-
else
-
super
-
end
-
end
-
end
-
end
-
1
require 'active_support/dependencies'
-
-
1
module AbstractController
-
1
module Helpers
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
4
class_attribute :_helpers
-
4
self._helpers = Module.new
-
-
4
class_attribute :_helper_methods
-
4
self._helper_methods = Array.new
-
end
-
-
1
module ClassMethods
-
# When a class is inherited, wrap its helper module in a new module.
-
# This ensures that the parent class's module can be changed
-
# independently of the child class's.
-
1
def inherited(klass)
-
361
helpers = _helpers
-
722
klass._helpers = Module.new { include helpers }
-
722
klass.class_eval { default_helper_module! unless anonymous? }
-
361
super
-
end
-
-
# Declare a controller method as a helper. For example, the following
-
# makes the +current_user+ controller method available to the view:
-
# class ApplicationController < ActionController::Base
-
# helper_method :current_user, :logged_in?
-
#
-
# def current_user
-
# @current_user ||= User.find_by_id(session[:user])
-
# end
-
#
-
# def logged_in?
-
# current_user != nil
-
# end
-
# end
-
#
-
# In a view:
-
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
-
#
-
# ==== Parameters
-
# * <tt>method[, method]</tt> - A name or names of a method on the controller
-
# to be made available on the view.
-
1
def helper_method(*meths)
-
19
meths.flatten!
-
19
self._helper_methods += meths
-
-
19
meths.each do |meth|
-
20
_helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
-
def #{meth}(*args, &blk) # def current_user(*args, &blk)
-
controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk)
-
end # end
-
ruby_eval
-
end
-
end
-
-
# The +helper+ class method can take a series of helper module names, a block, or both.
-
#
-
# ==== Parameters
-
# * <tt>*args</tt> - Module, Symbol, String, :all
-
# * <tt>block</tt> - A block defining helper methods
-
#
-
# ==== Examples
-
# When the argument is a module it will be included directly in the template class.
-
# helper FooHelper # => includes FooHelper
-
#
-
# When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
-
# and include the module in the template class. The second form illustrates how to include custom helpers
-
# when working with namespaced controllers, or other cases where the file containing the helper definition is not
-
# in one of Rails' standard load paths:
-
# helper :foo # => requires 'foo_helper' and includes FooHelper
-
# helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
-
#
-
# Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
-
# to the template.
-
#
-
# # One line
-
# helper { def hello() "Hello, world!" end }
-
#
-
# # Multi-line
-
# helper do
-
# def foo(bar)
-
# "#{bar} is the very best"
-
# end
-
# end
-
#
-
# Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
-
# +symbols+, +strings+, +modules+ and blocks.
-
#
-
# helper(:three, BlindHelper) { def mice() 'mice' end }
-
#
-
1
def helper(*args, &block)
-
1262
modules_for_helpers(args).each do |mod|
-
914
add_template_helper(mod)
-
end
-
-
911
_helpers.module_eval(&block) if block_given?
-
end
-
-
# Clears up all existing helpers in this class, only keeping the helper
-
# with the same name as this class.
-
1
def clear_helpers
-
2
inherited_helper_methods = _helper_methods
-
2
self._helpers = Module.new
-
2
self._helper_methods = Array.new
-
-
8
inherited_helper_methods.each { |meth| helper_method meth }
-
2
default_helper_module! unless anonymous?
-
end
-
-
# Returns a list of modules, normalized from the acceptable kinds of
-
# helpers with the following behavior:
-
#
-
# String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper",
-
# and "foo_bar_helper.rb" is loaded using require_dependency.
-
#
-
# Module:: No further processing
-
#
-
# After loading the appropriate files, the corresponding modules
-
# are returned.
-
#
-
# ==== Parameters
-
# * <tt>args</tt> - An array of helpers
-
#
-
# ==== Returns
-
# * <tt>Array</tt> - A normalized list of modules for the list of
-
# helpers provided.
-
1
def modules_for_helpers(args)
-
1262
args.flatten.map! do |arg|
-
1265
case arg
-
when String, Symbol
-
369
file_name = "#{arg.to_s.underscore}_helper"
-
369
begin
-
369
require_dependency(file_name)
-
rescue LoadError => e
-
351
raise MissingHelperError.new(e, file_name)
-
end
-
18
file_name.camelize.constantize
-
when Module
-
896
arg
-
else
-
raise ArgumentError, "helper must be a String, Symbol, or Module"
-
end
-
end
-
end
-
-
1
class MissingHelperError < LoadError
-
1
def initialize(error, path)
-
351
@error = error
-
351
@path = "helpers/#{path}.rb"
-
351
set_backtrace error.backtrace
-
351
super("Missing helper file helpers/%s.rb" % path)
-
end
-
end
-
-
1
private
-
# Makes all the (instance) methods in the helper module available to templates
-
# rendered through this controller.
-
#
-
# ==== Parameters
-
# * <tt>module</tt> - The module to include into the current helper module
-
# for the class
-
1
def add_template_helper(mod)
-
1828
_helpers.module_eval { include mod }
-
end
-
-
1
def default_helper_module!
-
358
module_name = name.sub(/Controller$/, '')
-
358
module_path = module_name.underscore
-
358
helper module_path
-
rescue MissingSourceFile => e
-
350
raise e unless e.is_missing? "helpers/#{module_path}_helper"
-
rescue NameError => e
-
raise e unless e.missing_name? "#{module_name}Helper"
-
end
-
end
-
end
-
end
-
1
require "active_support/core_ext/module/remove_method"
-
-
1
module AbstractController
-
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
-
# repeated setups. The inclusion pattern has pages that look like this:
-
#
-
# <%= render "shared/header" %>
-
# Hello World
-
# <%= render "shared/footer" %>
-
#
-
# This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
-
# and if you ever want to change the structure of these two includes, you'll have to change all the templates.
-
#
-
# With layouts, you can flip it around and have the common structure know where to insert changing content. This means
-
# that the header and footer are only mentioned in one place, like this:
-
#
-
# // The header part of this layout
-
# <%= yield %>
-
# // The footer part of this layout
-
#
-
# And then you have content pages that look like this:
-
#
-
# hello world
-
#
-
# At rendering time, the content page is computed and then inserted in the layout, like this:
-
#
-
# // The header part of this layout
-
# hello world
-
# // The footer part of this layout
-
#
-
# == Accessing shared variables
-
#
-
# Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
-
# references that won't materialize before rendering time:
-
#
-
# <h1><%= @page_title %></h1>
-
# <%= yield %>
-
#
-
# ...and content pages that fulfill these references _at_ rendering time:
-
#
-
# <% @page_title = "Welcome" %>
-
# Off-world colonies offers you a chance to start a new life
-
#
-
# The result after rendering is:
-
#
-
# <h1>Welcome</h1>
-
# Off-world colonies offers you a chance to start a new life
-
#
-
# == Layout assignment
-
#
-
# You can either specify a layout declaratively (using the #layout class method) or give
-
# it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
-
# If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
-
#
-
# For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
-
# that template will be used for all actions in PostsController and controllers inheriting
-
# from PostsController.
-
#
-
# If you use a module, for instance Weblog::PostsController, you will need a template named
-
# <tt>app/views/layouts/weblog/posts.html.erb</tt>.
-
#
-
# Since all your controllers inherit from ApplicationController, they will use
-
# <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
-
# or provided.
-
#
-
# == Inheritance Examples
-
#
-
# class BankController < ActionController::Base
-
# # bank.html.erb exists
-
#
-
# class ExchangeController < BankController
-
# # exchange.html.erb exists
-
#
-
# class CurrencyController < BankController
-
#
-
# class InformationController < BankController
-
# layout "information"
-
#
-
# class TellerController < InformationController
-
# # teller.html.erb exists
-
#
-
# class EmployeeController < InformationController
-
# # employee.html.erb exists
-
# layout nil
-
#
-
# class VaultController < BankController
-
# layout :access_level_layout
-
#
-
# class TillController < BankController
-
# layout false
-
#
-
# In these examples, we have three implicit lookup scenarios:
-
# * The BankController uses the "bank" layout.
-
# * The ExchangeController uses the "exchange" layout.
-
# * The CurrencyController inherits the layout from BankController.
-
#
-
# However, when a layout is explicitly set, the explicitly set layout wins:
-
# * The InformationController uses the "information" layout, explicitly set.
-
# * The TellerController also uses the "information" layout, because the parent explicitly set it.
-
# * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration.
-
# * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
-
# * The TillController does not use a layout at all.
-
#
-
# == Types of layouts
-
#
-
# Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
-
# you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
-
# be done either by specifying a method reference as a symbol or using an inline method (as a proc).
-
#
-
# The method reference is the preferred approach to variable layouts and is used like this:
-
#
-
# class WeblogController < ActionController::Base
-
# layout :writers_and_readers
-
#
-
# def index
-
# # fetching posts
-
# end
-
#
-
# private
-
# def writers_and_readers
-
# logged_in? ? "writer_layout" : "reader_layout"
-
# end
-
# end
-
#
-
# Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
-
# is logged in or not.
-
#
-
# If you want to use an inline method, such as a proc, do something like this:
-
#
-
# class WeblogController < ActionController::Base
-
# layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
-
# end
-
#
-
# If an argument isn't given to the proc, it's evaluated in the context of
-
# the current controller anyway.
-
#
-
# class WeblogController < ActionController::Base
-
# layout proc { logged_in? ? "writer_layout" : "reader_layout" }
-
# end
-
#
-
# Of course, the most common way of specifying a layout is still just as a plain template name:
-
#
-
# class WeblogController < ActionController::Base
-
# layout "weblog_standard"
-
# end
-
#
-
# The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point
-
# <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>.
-
#
-
# Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
-
# Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent:
-
#
-
# class ApplicationController < ActionController::Base
-
# layout "application"
-
# end
-
#
-
# class PostsController < ApplicationController
-
# # Will use "application" layout
-
# end
-
#
-
# class CommentsController < ApplicationController
-
# # Will search for "comments" layout and fallback "application" layout
-
# layout nil
-
# end
-
#
-
# == Conditional layouts
-
#
-
# If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
-
# a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
-
# <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
-
#
-
# class WeblogController < ActionController::Base
-
# layout "weblog_standard", :except => :rss
-
#
-
# # ...
-
#
-
# end
-
#
-
# This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will
-
# be rendered directly, without wrapping a layout around the rendered view.
-
#
-
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
-
# #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
-
#
-
# == Using a different layout in the action render call
-
#
-
# If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
-
# Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
-
# You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
-
#
-
# class WeblogController < ActionController::Base
-
# layout "weblog_standard"
-
#
-
# def help
-
# render :action => "help", :layout => "help"
-
# end
-
# end
-
#
-
# This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead.
-
1
module Layouts
-
1
extend ActiveSupport::Concern
-
-
1
include Rendering
-
-
1
included do
-
4
class_attribute :_layout, :_layout_conditions, :instance_accessor => false
-
4
self._layout = nil
-
4
self._layout_conditions = {}
-
4
_write_layout_method
-
end
-
-
1
delegate :_layout_conditions, :to => "self.class"
-
-
1
module ClassMethods
-
1
def inherited(klass)
-
332
super
-
332
klass._write_layout_method
-
end
-
-
# This module is mixed in if layout conditions are provided. This means
-
# that if no layout conditions are used, this method is not used
-
1
module LayoutConditions
-
# Determines whether the current action has a layout by checking the
-
# action name against the :only and :except conditions set on the
-
# layout.
-
#
-
# ==== Returns
-
# * <tt> Boolean</tt> - True if the action has a layout, false otherwise.
-
1
def conditional_layout?
-
12
return unless super
-
-
12
conditions = _layout_conditions
-
-
12
if only = conditions[:only]
-
6
only.include?(action_name)
-
6
elsif except = conditions[:except]
-
6
!except.include?(action_name)
-
else
-
true
-
end
-
end
-
end
-
-
# Specify the layout to use for this class.
-
#
-
# If the specified layout is a:
-
# String:: the String is the template name
-
# Symbol:: call the method specified by the symbol, which will return the template name
-
# false:: There is no layout
-
# true:: raise an ArgumentError
-
# nil:: Force default layout behavior with inheritance
-
#
-
# ==== Parameters
-
# * <tt>layout</tt> - The layout to use.
-
#
-
# ==== Options (conditions)
-
# * :only - A list of actions to apply this layout to.
-
# * :except - Apply this layout to all actions but this one.
-
1
def layout(layout, conditions = {})
-
30
include LayoutConditions unless conditions.empty?
-
-
38
conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
-
30
self._layout_conditions = conditions
-
-
30
self._layout = layout
-
30
_write_layout_method
-
end
-
-
# If no layout is supplied, look for a template named the return
-
# value of this method.
-
#
-
# ==== Returns
-
# * <tt>String</tt> - A template name
-
1
def _implied_layout_name
-
678
controller_path
-
end
-
-
# Creates a _layout method to be called by _default_layout .
-
#
-
# If a layout is not explicitly mentioned then look for a layout with the controller's name.
-
# if nothing is found then try same procedure to find super class's layout.
-
1
def _write_layout_method
-
366
remove_possible_method(:_layout)
-
-
366
prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
-
366
name_clause = if name
-
<<-RUBY
-
360
lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
-
RUBY
-
else
-
6
<<-RUBY
-
super
-
RUBY
-
end
-
-
366
layout_definition = case _layout
-
when String
-
18
_layout.inspect
-
when Symbol
-
<<-RUBY
-
7
#{_layout}.tap do |layout|
-
unless layout.is_a?(String) || !layout
-
raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
-
"should have returned a String, false, or nil"
-
end
-
end
-
RUBY
-
when Proc
-
3
define_method :_layout_from_proc, &_layout
-
3
_layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)"
-
when false
-
1
nil
-
when true
-
1
raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
-
when nil
-
336
name_clause
-
end
-
-
365
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def _layout
-
if conditional_layout?
-
#{layout_definition}
-
else
-
#{name_clause}
-
end
-
end
-
private :_layout
-
RUBY
-
end
-
end
-
-
1
def _normalize_options(options)
-
1095
super
-
-
1095
if _include_layout?(options)
-
678
layout = options.delete(:layout) { :default }
-
383
options[:layout] = _layout_for_option(layout)
-
end
-
end
-
-
1
attr_internal_writer :action_has_layout
-
-
1
def initialize(*)
-
2664
@_action_has_layout = true
-
2664
super
-
end
-
-
1
def action_has_layout?
-
252
@_action_has_layout
-
end
-
-
1
def conditional_layout?
-
380
true
-
end
-
-
1
private
-
-
# This will be overwritten by _write_layout_method
-
1
def _layout; end
-
-
# Determine the layout for a given name, taking into account the name type.
-
#
-
# ==== Parameters
-
# * <tt>name</tt> - The name of the template
-
1
def _layout_for_option(name)
-
383
case name
-
31
when String then _normalize_layout(name)
-
11
when Proc then name
-
18
when true then Proc.new { _default_layout(true) }
-
530
when :default then Proc.new { _default_layout(false) }
-
when false, nil then nil
-
else
-
raise ArgumentError,
-
"String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}"
-
end
-
end
-
-
1
def _normalize_layout(value)
-
271
value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value
-
end
-
-
# Returns the default layout for this controller.
-
# Optionally raises an exception if the layout could not be found.
-
#
-
# ==== Parameters
-
# * <tt>require_layout</tt> - If set to true and layout is not found,
-
# an ArgumentError exception is raised (defaults to false)
-
#
-
# ==== Returns
-
# * <tt>template</tt> - The template object for the default layout (or nil)
-
1
def _default_layout(require_layout = false)
-
243
begin
-
243
value = _layout if action_has_layout?
-
rescue NameError => e
-
1
raise e, "Could not render layout: #{e.message}"
-
end
-
-
241
if require_layout && action_has_layout? && !value
-
1
raise ArgumentError,
-
"There was no default layout for #{self.class} in #{view_paths.inspect}"
-
end
-
-
240
_normalize_layout(value)
-
end
-
-
1
def _include_layout?(options)
-
1095
(options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
-
end
-
end
-
end
-
1
require "active_support/benchmarkable"
-
-
1
module AbstractController
-
1
module Logger #:nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
2
config_accessor :logger
-
2
include ActiveSupport::Benchmarkable
-
end
-
end
-
end
-
1
require "abstract_controller/base"
-
1
require "action_view"
-
-
1
module AbstractController
-
1
class DoubleRenderError < Error
-
1
DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."
-
-
1
def initialize(message = nil)
-
4
super(message || DEFAULT_MESSAGE)
-
end
-
end
-
-
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
-
# it will trigger the lookup_context and consequently expire the cache.
-
1
class I18nProxy < ::I18n::Config #:nodoc:
-
1
attr_reader :original_config, :lookup_context
-
-
1
def initialize(original_config, lookup_context)
-
1577
original_config = original_config.original_config if original_config.respond_to?(:original_config)
-
1577
@original_config, @lookup_context = original_config, lookup_context
-
end
-
-
1
def locale
-
7
@original_config.locale
-
end
-
-
1
def locale=(value)
-
2
@lookup_context.locale = value
-
end
-
end
-
-
1
module Rendering
-
1
extend ActiveSupport::Concern
-
1
include AbstractController::ViewPaths
-
-
1
included do
-
7
class_attribute :protected_instance_variables
-
7
self.protected_instance_variables = []
-
end
-
-
# Overwrite process to setup I18n proxy.
-
1
def process(*) #:nodoc:
-
1576
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
1576
super
-
ensure
-
1576
I18n.config = old_config
-
end
-
-
1
module ClassMethods
-
1
def view_context_class
-
@view_context_class ||= begin
-
218
routes = respond_to?(:_routes) && _routes
-
218
helpers = respond_to?(:_helpers) && _helpers
-
-
218
Class.new(ActionView::Base) do
-
218
if routes
-
189
include routes.url_helpers
-
189
include routes.mounted_helpers
-
end
-
-
218
if helpers
-
193
include helpers
-
end
-
end
-
1227
end
-
end
-
end
-
-
1
attr_internal_writer :view_context_class
-
-
1
def view_context_class
-
1356
@_view_context_class ||= self.class.view_context_class
-
end
-
-
# An instance of a view class. The default view class is ActionView::Base
-
#
-
# The view class must have the following methods:
-
# View.new[lookup_context, assigns, controller]
-
# Create a new ActionView instance for a controller
-
# View#render[options]
-
# Returns String with the rendered template
-
#
-
# Override this method in a module to change the default behavior.
-
1
def view_context
-
1300
view_context_class.new(view_renderer, view_assigns, self)
-
end
-
-
# Returns an object that is able to render templates.
-
1
def view_renderer
-
2378
@_view_renderer ||= ActionView::Renderer.new(lookup_context)
-
end
-
-
# Normalize arguments, options and then delegates render_to_body and
-
# sticks the result in self.response_body.
-
1
def render(*args, &block)
-
1101
options = _normalize_render(*args, &block)
-
1101
self.response_body = render_to_body(options)
-
end
-
-
# Raw rendering of a template to a string. Just convert the results of
-
# render_response into a String.
-
# :api: plugin
-
1
def render_to_string(*args, &block)
-
16
options = _normalize_render(*args, &block)
-
16
render_to_body(options)
-
end
-
-
# Raw rendering of a template to a Rack-compatible body.
-
# :api: plugin
-
1
def render_to_body(options = {})
-
1078
_process_options(options)
-
1078
_render_template(options)
-
end
-
-
# Find and renders a template based on the options given.
-
# :api: private
-
1
def _render_template(options) #:nodoc:
-
1070
lookup_context.rendered_format = nil if options[:formats]
-
1070
view_renderer.render(view_context, options)
-
end
-
-
1
DEFAULT_PROTECTED_INSTANCE_VARIABLES = [
-
:@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config,
-
:@_view_context_class, :@_view_renderer, :@_lookup_context
-
]
-
-
# This method should return a hash with assigns.
-
# You can overwrite this configuration per controller.
-
# :api: public
-
1
def view_assigns
-
2537
hash = {}
-
2537
variables = instance_variables
-
2537
variables -= protected_instance_variables
-
2537
variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES
-
6205
variables.each { |name| hash[name[1..-1]] = instance_variable_get(name) }
-
2537
hash
-
end
-
-
1
private
-
-
# Normalize args and options.
-
# :api: private
-
1
def _normalize_render(*args, &block)
-
1117
options = _normalize_args(*args, &block)
-
1117
_normalize_options(options)
-
1117
options
-
end
-
-
# Normalize args by converting render "foo" to render :action => "foo" and
-
# render "foo/bar" to render :file => "foo/bar".
-
# :api: plugin
-
1
def _normalize_args(action=nil, options={})
-
1117
case action
-
when NilClass
-
when Hash
-
1024
options = action
-
when String, Symbol
-
25
action = action.to_s
-
25
key = action.include?(?/) ? :file : :action
-
25
options[key] = action
-
else
-
1
options[:partial] = action
-
end
-
-
1117
options
-
end
-
-
# Normalize options.
-
# :api: plugin
-
1
def _normalize_options(options)
-
1117
if options[:partial] == true
-
1
options[:partial] = action_name
-
end
-
-
1117
if (options.keys & [:partial, :file, :template]).empty?
-
963
options[:prefixes] ||= _prefixes
-
end
-
-
1117
options[:template] ||= (options[:action] || action_name).to_s
-
1117
options
-
end
-
-
# Process extra options.
-
# :api: plugin
-
1
def _process_options(options)
-
end
-
end
-
end
-
1
module AbstractController
-
1
module Translation
-
1
def translate(*args)
-
2
key = args.first
-
2
if key.is_a?(String) && (key[0] == '.')
-
1
key = "#{ controller_path.gsub('/', '.') }.#{ action_name }#{ key }"
-
1
args[0] = key
-
end
-
-
2
I18n.translate(*args)
-
end
-
1
alias :t :translate
-
-
1
def localize(*args)
-
I18n.localize(*args)
-
end
-
1
alias :l :localize
-
end
-
end
-
1
module AbstractController
-
# Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class
-
# has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an
-
# exception will be raised.
-
#
-
# Note that this module is completely decoupled from HTTP - the only requirement is a valid
-
# <tt>_routes</tt> implementation.
-
1
module UrlFor
-
1
extend ActiveSupport::Concern
-
1
include ActionDispatch::Routing::UrlFor
-
-
1
def _routes
-
raise "In order to use #url_for, you must include routing helpers explicitly. " \
-
"For instance, `include Rails.application.routes.url_helpers"
-
end
-
-
1
module ClassMethods
-
1
def _routes
-
nil
-
end
-
-
1
def action_methods
-
@action_methods ||= begin
-
381
if _routes
-
381
super - _routes.named_routes.helper_names
-
else
-
super
-
end
-
3023
end
-
end
-
end
-
end
-
end
-
1
require 'action_view/base'
-
-
1
module AbstractController
-
1
module ViewPaths
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
7
class_attribute :_view_paths
-
7
self._view_paths = ActionView::PathSet.new
-
7
self._view_paths.freeze
-
end
-
-
1
delegate :template_exists?, :view_paths, :formats, :formats=,
-
:locale, :locale=, :to => :lookup_context
-
-
1
module ClassMethods
-
1
def parent_prefixes
-
@parent_prefixes ||= begin
-
244
parent_controller = superclass
-
244
prefixes = []
-
-
244
until parent_controller.abstract?
-
135
prefixes << parent_controller.controller_path
-
135
parent_controller = parent_controller.superclass
-
end
-
-
244
prefixes
-
1547
end
-
end
-
end
-
-
# The prefixes used in render "foo" shortcuts.
-
1
def _prefixes
-
@_prefixes ||= begin
-
1547
parent_prefixes = self.class.parent_prefixes
-
1547
parent_prefixes.dup.unshift(controller_path)
-
2541
end
-
end
-
-
# LookupContext is the object responsible to hold all information required to lookup
-
# templates, i.e. view paths and details. Check ActionView::LookupContext for more
-
# information.
-
1
def lookup_context
-
@_lookup_context ||=
-
7635
ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
-
end
-
-
1
def details_for_lookup
-
1564
{ }
-
end
-
-
1
def append_view_path(path)
-
5
lookup_context.view_paths.push(*path)
-
end
-
-
1
def prepend_view_path(path)
-
7
lookup_context.view_paths.unshift(*path)
-
end
-
-
1
module ClassMethods
-
# Append a path to the list of view paths for this controller.
-
#
-
# ==== Parameters
-
# * <tt>path</tt> - If a String is provided, it gets converted into
-
# the default view path. You may also provide a custom view path
-
# (see ActionView::PathSet for more information)
-
1
def append_view_path(path)
-
3
self._view_paths = view_paths + Array(path)
-
end
-
-
# Prepend a path to the list of view paths for this controller.
-
#
-
# ==== Parameters
-
# * <tt>path</tt> - If a String is provided, it gets converted into
-
# the default view path. You may also provide a custom view path
-
# (see ActionView::PathSet for more information)
-
1
def prepend_view_path(path)
-
2
self._view_paths = ActionView::PathSet.new(Array(path) + view_paths)
-
end
-
-
# A list of all of the default view paths for this controller.
-
1
def view_paths
-
167
_view_paths
-
end
-
-
# Set the view paths.
-
#
-
# ==== Parameters
-
# * <tt>paths</tt> - If a PathSet is provided, use that;
-
# otherwise, process the parameter into a PathSet.
-
1
def view_paths=(paths)
-
47
self._view_paths = ActionView::PathSet.new(Array(paths))
-
end
-
end
-
end
-
end
-
1
require 'active_support/rails'
-
1
require 'abstract_controller'
-
1
require 'action_dispatch'
-
1
require 'action_controller/metal/live'
-
1
require 'action_controller/metal/strong_parameters'
-
-
1
module ActionController
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Base
-
1
autoload :Caching
-
1
autoload :Metal
-
1
autoload :Middleware
-
-
1
autoload_under "metal" do
-
1
autoload :Compatibility
-
1
autoload :ConditionalGet
-
1
autoload :Cookies
-
1
autoload :DataStreaming
-
1
autoload :Flash
-
1
autoload :ForceSSL
-
1
autoload :Head
-
1
autoload :Helpers
-
1
autoload :HideActions
-
1
autoload :HttpAuthentication
-
1
autoload :ImplicitRender
-
1
autoload :Instrumentation
-
1
autoload :MimeResponds
-
1
autoload :ParamsWrapper
-
1
autoload :RackDelegation
-
1
autoload :Redirecting
-
1
autoload :Renderers
-
1
autoload :Rendering
-
1
autoload :RequestForgeryProtection
-
1
autoload :Rescue
-
1
autoload :Responder
-
1
autoload :Streaming
-
1
autoload :StrongParameters
-
1
autoload :Testing
-
1
autoload :UrlFor
-
end
-
-
1
autoload :Integration, 'action_controller/deprecated/integration_test'
-
1
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
-
1
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
-
1
autoload :Routing, 'action_controller/deprecated'
-
1
autoload :TestCase, 'action_controller/test_case'
-
1
autoload :TemplateAssertions, 'action_controller/test_case'
-
-
1
eager_autoload do
-
1
autoload :RecordIdentifier
-
end
-
-
1
def self.eager_load!
-
super
-
ActionController::Caching.eager_load!
-
HTML.eager_load!
-
end
-
end
-
-
# All of these simply register additional autoloads
-
1
require 'action_view'
-
1
require 'action_view/vendor/html-scanner'
-
-
1
ActiveSupport.on_load(:action_view) do
-
1
ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
-
end
-
-
# Common Active Support usage in Action Controller
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/load_error'
-
1
require 'active_support/core_ext/module/attr_internal'
-
1
require 'active_support/core_ext/name_error'
-
1
require 'active_support/core_ext/uri'
-
1
require 'active_support/inflector'
-
1
require "action_controller/log_subscriber"
-
-
1
module ActionController
-
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
-
# on request and then either render a template or redirect to another action. An action is defined as a public method
-
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
-
#
-
# By default, only the ApplicationController in a \Rails application inherits from <tt>ActionController::Base</tt>. All other
-
# controllers in turn inherit from ApplicationController. This gives you one class to configure things such as
-
# request forgery protection and filtering of sensitive request parameters.
-
#
-
# A sample controller could look like this:
-
#
-
# class PostsController < ApplicationController
-
# def index
-
# @posts = Post.all
-
# end
-
#
-
# def create
-
# @post = Post.create params[:post]
-
# redirect_to posts_path
-
# end
-
# end
-
#
-
# Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
-
# after executing code in the action. For example, the +index+ action of the PostsController would render the
-
# template <tt>app/views/posts/index.html.erb</tt> by default after populating the <tt>@posts</tt> instance variable.
-
#
-
# Unlike index, the create action will not render a template. After performing its main purpose (creating a
-
# new post), it initiates a redirect instead. This redirect works by returning an external
-
# "302 Moved" HTTP response that takes the user to the index action.
-
#
-
# These two methods represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
-
# Most actions are variations on these themes.
-
#
-
# == Requests
-
#
-
# For every request, the router determines the value of the +controller+ and +action+ keys. These determine which controller
-
# and action are called. The remaining request parameters, the session (if one is available), and the full request with
-
# all the HTTP headers are made available to the action through accessor methods. Then the action is performed.
-
#
-
# The full request object is available via the request accessor and is primarily used to query for HTTP headers:
-
#
-
# def server_ip
-
# location = request.env["SERVER_ADDR"]
-
# render text: "This server hosted at #{location}"
-
# end
-
#
-
# == Parameters
-
#
-
# All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method
-
# which returns a hash. For example, an action that was performed through <tt>/posts?category=All&limit=5</tt> will include
-
# <tt>{ "category" => "All", "limit" => "5" }</tt> in params.
-
#
-
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
-
#
-
# <input type="text" name="post[name]" value="david">
-
# <input type="text" name="post[address]" value="hyacintvej">
-
#
-
# A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
-
# If the address input had been named "post[address][street]", the params would have included
-
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
-
#
-
# == Sessions
-
#
-
# Sessions allow you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
-
# such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
-
# as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
-
# they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
-
#
-
# You can place objects in the session by using the <tt>session</tt> method, which accesses a hash:
-
#
-
# session[:person] = Person.authenticate(user_name, password)
-
#
-
# And retrieved again through the same hash:
-
#
-
# Hello #{session[:person]}
-
#
-
# For removing objects from the session, you can either assign a single key to +nil+:
-
#
-
# # removes :person from session
-
# session[:person] = nil
-
#
-
# or you can remove the entire session with +reset_session+.
-
#
-
# Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted.
-
# This prevents the user from tampering with the session but also allows him to see its contents.
-
#
-
# Do not put secret information in cookie-based sessions!
-
#
-
# == Responses
-
#
-
# Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
-
# object is generated automatically through the use of renders and redirects and requires no user intervention.
-
#
-
# == Renders
-
#
-
# Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
-
# of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured.
-
# The controller passes objects to the view by assigning instance variables:
-
#
-
# def show
-
# @post = Post.find(params[:id])
-
# end
-
#
-
# Which are then automatically available to the view:
-
#
-
# Title: <%= @post.title %>
-
#
-
# You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
-
# will use the manual rendering methods:
-
#
-
# def search
-
# @results = Search.find(params[:query])
-
# case @results.count
-
# when 0 then render action: "no_results"
-
# when 1 then render action: "show"
-
# when 2..10 then render action: "show_many"
-
# end
-
# end
-
#
-
# Read more about writing ERB and Builder templates in ActionView::Base.
-
#
-
# == Redirects
-
#
-
# Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the
-
# database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
-
# going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
-
#
-
# def create
-
# @entry = Entry.new(params[:entry])
-
# if @entry.save
-
# # The entry was saved correctly, redirect to show
-
# redirect_to action: 'show', id: @entry.id
-
# else
-
# # things didn't go so well, do something else
-
# end
-
# end
-
#
-
# In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method, which is then executed.
-
# Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action),
-
# and not some internal re-routing which calls both "create" and then "show" within one request.
-
#
-
# Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting.
-
#
-
# == Calling multiple redirects or renders
-
#
-
# An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError:
-
#
-
# def do_something
-
# redirect_to action: "elsewhere"
-
# render action: "overthere" # raises DoubleRenderError
-
# end
-
#
-
# If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
-
#
-
# def do_something
-
# redirect_to(action: "elsewhere") and return if monkeys.nil?
-
# render action: "overthere" # won't be called if monkeys is nil
-
# end
-
#
-
1
class Base < Metal
-
1
abstract!
-
-
# We document the request and response methods here because albeit they are
-
# implemented in ActionController::Metal, the type of the returned objects
-
# is unknown at that level.
-
-
##
-
# :method: request
-
#
-
# Returns an ActionDispatch::Request instance that represents the
-
# current request.
-
-
##
-
# :method: response
-
#
-
# Returns an ActionDispatch::Response that represents the current
-
# response.
-
-
# Shortcut helper that returns all the modules included in
-
# ActionController::Base except the ones passed as arguments:
-
#
-
# class MetalController
-
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
-
# include left
-
# end
-
# end
-
#
-
# This gives better control over what you want to exclude and makes it
-
# easier to create a bare controller class, instead of listing the modules
-
# required manually.
-
1
def self.without_modules(*modules)
-
modules = modules.map do |m|
-
m.is_a?(Symbol) ? ActionController.const_get(m) : m
-
end
-
-
MODULES - modules
-
end
-
-
1
MODULES = [
-
AbstractController::Layouts,
-
AbstractController::Translation,
-
AbstractController::AssetPaths,
-
-
Helpers,
-
HideActions,
-
UrlFor,
-
Redirecting,
-
Rendering,
-
Renderers::All,
-
ConditionalGet,
-
RackDelegation,
-
Caching,
-
MimeResponds,
-
ImplicitRender,
-
StrongParameters,
-
-
Cookies,
-
Flash,
-
RequestForgeryProtection,
-
ForceSSL,
-
Streaming,
-
DataStreaming,
-
RecordIdentifier,
-
HttpAuthentication::Basic::ControllerMethods,
-
HttpAuthentication::Digest::ControllerMethods,
-
HttpAuthentication::Token::ControllerMethods,
-
-
# Before callbacks should also be executed the earliest as possible, so
-
# also include them at the bottom.
-
AbstractController::Callbacks,
-
-
# Append rescue at the bottom to wrap as much as possible.
-
Rescue,
-
-
# Add instrumentations hooks at the bottom, to ensure they instrument
-
# all the methods properly.
-
Instrumentation,
-
-
# Params wrapper should come before instrumentation so they are
-
# properly showed in logs
-
ParamsWrapper
-
]
-
-
1
MODULES.each do |mod|
-
29
include mod
-
end
-
-
# Define some internal variables that should not be propagated to the view.
-
1
self.protected_instance_variables = [
-
:@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
-
:@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
-
]
-
-
1
ActiveSupport.run_load_hooks(:action_controller, self)
-
end
-
end
-
1
require 'fileutils'
-
1
require 'uri'
-
1
require 'set'
-
-
1
module ActionController
-
# \Caching is a cheap way of speeding up slow applications by keeping the result of
-
# calculations, renderings, and database calls around for subsequent requests.
-
#
-
# You can read more about each approach and the sweeping assistance by clicking the
-
# modules below.
-
#
-
# Note: To turn off all caching and sweeping, set
-
# config.action_controller.perform_caching = false.
-
#
-
# == \Caching stores
-
#
-
# All the caching stores from ActiveSupport::Cache are available to be used as backends
-
# for Action Controller caching.
-
#
-
# Configuration examples (MemoryStore is the default):
-
#
-
# config.action_controller.cache_store = :memory_store
-
# config.action_controller.cache_store = :file_store, '/path/to/cache/directory'
-
# config.action_controller.cache_store = :mem_cache_store, 'localhost'
-
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
-
# config.action_controller.cache_store = MyOwnStore.new('parameter')
-
1
module Caching
-
1
extend ActiveSupport::Concern
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :Fragments
-
1
autoload :Sweeper, 'action_controller/caching/sweeping'
-
1
autoload :Sweeping, 'action_controller/caching/sweeping'
-
end
-
-
1
module ConfigMethods
-
1
def cache_store
-
46
config.cache_store
-
end
-
-
1
def cache_store=(store)
-
34
config.cache_store = ActiveSupport::Cache.lookup_store(store)
-
end
-
-
1
private
-
1
def cache_configured?
-
27
perform_caching && cache_store
-
end
-
end
-
-
1
include RackDelegation
-
1
include AbstractController::Callbacks
-
-
1
include ConfigMethods
-
1
include Fragments
-
1
include Sweeping if defined?(ActiveRecord)
-
-
1
included do
-
2
extend ConfigMethods
-
-
2
config_accessor :default_static_extension
-
2
self.default_static_extension ||= '.html'
-
-
2
def self.page_cache_extension=(extension)
-
1
ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
-
1
self.default_static_extension = extension
-
end
-
-
2
def self.page_cache_extension
-
ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
-
default_static_extension
-
end
-
-
2
config_accessor :perform_caching
-
2
self.perform_caching = true if perform_caching.nil?
-
end
-
-
1
def caching_allowed?
-
request.get? && response.status == 200
-
end
-
-
1
protected
-
# Convenience accessor.
-
1
def cache(key, options = {}, &block)
-
if cache_configured?
-
cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
-
else
-
yield
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
module Caching
-
# Fragment caching is used for caching various blocks within
-
# views without caching the entire action as a whole. This is
-
# useful when certain elements of an action change frequently or
-
# depend on complicated state while other parts rarely change or
-
# can be shared amongst multiple parties. The caching is done using
-
# the +cache+ helper available in the Action View. See
-
# ActionView::Helpers::CacheHelper for more information.
-
#
-
# While it's strongly recommended that you use key-based cache
-
# expiration (see links in CacheHelper for more information),
-
# it is also possible to manually expire caches. For example:
-
#
-
# expire_fragment('name_of_cache')
-
1
module Fragments
-
# Given a key (as described in +expire_fragment+), returns
-
# a key suitable for use in reading, writing, or expiring a
-
# cached fragment. All keys are prefixed with <tt>views/</tt> and uses
-
# ActiveSupport::Cache.expand_cache_key for the expansion.
-
1
def fragment_cache_key(key)
-
25
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
-
end
-
-
# Writes +content+ to the location signified by
-
# +key+ (see +expire_fragment+ for acceptable formats).
-
1
def write_fragment(key, content, options = nil)
-
10
return content unless cache_configured?
-
-
9
key = fragment_cache_key(key)
-
9
instrument_fragment_cache :write_fragment, key do
-
9
content = content.to_str
-
9
cache_store.write(key, content, options)
-
end
-
9
content
-
end
-
-
# Reads a cached fragment from the location signified by +key+
-
# (see +expire_fragment+ for acceptable formats).
-
1
def read_fragment(key, options = nil)
-
11
return unless cache_configured?
-
-
10
key = fragment_cache_key(key)
-
10
instrument_fragment_cache :read_fragment, key do
-
10
result = cache_store.read(key, options)
-
10
result.respond_to?(:html_safe) ? result.html_safe : result
-
end
-
end
-
-
# Check if a cached fragment from the location signified by
-
# +key+ exists (see +expire_fragment+ for acceptable formats).
-
1
def fragment_exist?(key, options = nil)
-
4
return unless cache_configured?
-
2
key = fragment_cache_key(key)
-
-
2
instrument_fragment_cache :exist_fragment?, key do
-
2
cache_store.exist?(key, options)
-
end
-
end
-
-
# Removes fragments from the cache.
-
#
-
# +key+ can take one of three forms:
-
#
-
# * String - This would normally take the form of a path, like
-
# <tt>pages/45/notes</tt>.
-
# * Hash - Treated as an implicit call to +url_for+, like
-
# <tt>{ controller: 'pages', action: 'notes', id: 45}</tt>
-
# * Regexp - Will remove any fragment that matches, so
-
# <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
-
# don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
-
# the actual filename matched looks like
-
# <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
-
# only supported on caches that can iterate over all keys (unlike
-
# memcached).
-
#
-
# +options+ is passed through to the cache store's +delete+
-
# method (or <tt>delete_matched</tt>, for Regexp keys).
-
1
def expire_fragment(key, options = nil)
-
2
return unless cache_configured?
-
2
key = fragment_cache_key(key) unless key.is_a?(Regexp)
-
-
2
instrument_fragment_cache :expire_fragment, key do
-
2
if key.is_a?(Regexp)
-
1
cache_store.delete_matched(key, options)
-
else
-
1
cache_store.delete(key, options)
-
end
-
end
-
end
-
-
1
def instrument_fragment_cache(name, key) # :nodoc:
-
46
ActiveSupport::Notifications.instrument("#{name}.action_controller", :key => key){ yield }
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
module Caching
-
# Sweepers are the terminators of the caching world and responsible for expiring
-
# caches when Active Record objects change. They do this by being half-observers,
-
# half-filters and implementing callbacks for both roles.
-
#
-
# class ListSweeper < ActionController::Caching::Sweeper
-
# observe List, Item
-
#
-
# def after_save(record)
-
# list = record.is_a?(List) ? record : record.list
-
# expire_page(controller: 'lists', action: %w( show public feed ), id: list.id)
-
# expire_action(controller: 'lists', action: 'all')
-
# list.shares.each { |share| expire_page(controller: 'lists', action: 'show', id: share.url_key) }
-
# end
-
# end
-
#
-
# The sweeper is assigned in the controllers that wish to have its job performed using
-
# the +cache_sweeper+ class method:
-
#
-
# class ListsController < ApplicationController
-
# caches_action :index, :show, :public, :feed
-
# cache_sweeper :list_sweeper, only: [ :edit, :destroy, :share ]
-
# end
-
#
-
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
-
#
-
# You can also name an explicit class in the declaration of a sweeper, which is needed
-
# if the sweeper is in a module:
-
#
-
# class ListsController < ApplicationController
-
# caches_action :index, :show, :public, :feed
-
# cache_sweeper OpenBar::Sweeper, only: [ :edit, :destroy, :share ]
-
# end
-
1
module Sweeping
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods # :nodoc:
-
1
def cache_sweeper(*sweepers)
-
1
configuration = sweepers.extract_options!
-
-
1
sweepers.each do |sweeper|
-
1
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
-
1
sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.classify) : sweeper).instance
-
-
1
if sweeper_instance.is_a?(Sweeper)
-
1
around_filter(sweeper_instance, :only => configuration[:only])
-
else
-
after_filter(sweeper_instance, :only => configuration[:only])
-
end
-
end
-
end
-
end
-
end
-
-
1
if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
-
1
class Sweeper < ActiveRecord::Observer # :nodoc:
-
1
attr_accessor :controller
-
-
1
def initialize(*args)
-
5
super
-
5
@controller = nil
-
end
-
-
1
def before(controller)
-
3
self.controller = controller
-
3
callback(:before) if controller.perform_caching
-
3
true # before method from sweeper should always return true
-
end
-
-
1
def after(controller)
-
2
self.controller = controller
-
2
callback(:after) if controller.perform_caching
-
end
-
-
1
def around(controller)
-
2
before(controller)
-
2
yield
-
1
after(controller)
-
ensure
-
2
clean_up
-
end
-
-
1
protected
-
# gets the action cache path for the given options.
-
1
def action_path_for(options)
-
Actions::ActionCachePath.new(controller, options).path
-
end
-
-
# Retrieve instance variables set in the controller.
-
1
def assigns(key)
-
controller.instance_variable_get("@#{key}")
-
end
-
-
1
private
-
1
def clean_up
-
# Clean up, so that the controller can be collected after this request
-
2
self.controller = nil
-
end
-
-
1
def callback(timing)
-
5
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
-
5
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
-
-
5
__send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
-
5
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
-
end
-
-
1
def method_missing(method, *arguments, &block)
-
2
return super unless @controller
-
@controller.__send__(method, *arguments, &block)
-
end
-
end
-
end
-
end
-
end
-
-
1
module ActionController
-
1
class LogSubscriber < ActiveSupport::LogSubscriber
-
1
INTERNAL_PARAMS = %w(controller action format _method only_path)
-
-
1
def start_processing(event)
-
799
return unless logger.info?
-
-
799
payload = event.payload
-
799
params = payload[:params].except(*INTERNAL_PARAMS)
-
799
format = payload[:format]
-
799
format = format.to_s.upcase if format.is_a?(Symbol)
-
-
799
info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
-
799
info " Parameters: #{params.inspect}" unless params.empty?
-
end
-
-
1
def process_action(event)
-
799
return unless logger.info?
-
-
799
payload = event.payload
-
799
additions = ActionController::Base.log_process_action(payload)
-
-
799
status = payload[:status]
-
799
if status.nil? && payload[:exception].present?
-
22
exception_class_name = payload[:exception].first
-
22
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
-
end
-
799
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
-
799
message << " (#{additions.join(" | ")})" unless additions.blank?
-
-
799
info(message)
-
end
-
-
1
def halted_callback(event)
-
74
info("Filter chain halted as #{event.payload[:filter]} rendered or redirected")
-
end
-
-
1
def send_file(event)
-
1
info("Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)")
-
end
-
-
1
def redirect_to(event)
-
74
info("Redirected to #{event.payload[:location]}")
-
end
-
-
1
def send_data(event)
-
1
info("Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)")
-
end
-
-
1
%w(write_fragment read_fragment exist_fragment?
-
expire_fragment expire_page write_page).each do |method|
-
6
class_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{method}(event)
-
return unless logger.info?
-
key_or_path = event.payload[:key] || event.payload[:path]
-
human_name = #{method.to_s.humanize.inspect}
-
info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)")
-
end
-
METHOD
-
end
-
-
1
def logger
-
11817
ActionController::Base.logger
-
end
-
end
-
end
-
-
1
ActionController::LogSubscriber.attach_to :action_controller
-
1
require 'action_dispatch/middleware/stack'
-
-
1
module ActionController
-
# Extend ActionDispatch middleware stack to make it aware of options
-
# allowing the following syntax in controllers:
-
#
-
# class PostsController < ApplicationController
-
# use AuthenticationMiddleware, except: [:index, :show]
-
# end
-
#
-
1
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
-
1
class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
-
1
def initialize(klass, *args, &block)
-
7
options = args.extract_options!
-
7
@only = Array(options.delete(:only)).map(&:to_s)
-
7
@except = Array(options.delete(:except)).map(&:to_s)
-
7
args << options unless options.empty?
-
7
super
-
end
-
-
1
def valid?(action)
-
50
if @only.present?
-
4
@only.include?(action)
-
46
elsif @except.present?
-
4
!@except.include?(action)
-
else
-
42
true
-
end
-
end
-
end
-
-
1
def build(action, app=nil, &block)
-
373
app ||= block
-
373
action = action.to_s
-
373
raise "MiddlewareStack#build requires an app" unless app
-
-
373
middlewares.reverse.inject(app) do |a, middleware|
-
50
middleware.valid?(action) ?
-
middleware.build(a) : a
-
end
-
end
-
end
-
-
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
-
# valid Rack interface without the additional niceties provided by
-
# <tt>ActionController::Base</tt>.
-
#
-
# A sample metal controller might look like this:
-
#
-
# class HelloController < ActionController::Metal
-
# def index
-
# self.response_body = "Hello World!"
-
# end
-
# end
-
#
-
# And then to route requests to your metal controller, you would add
-
# something like this to <tt>config/routes.rb</tt>:
-
#
-
# match 'hello', to: HelloController.action(:index)
-
#
-
# The +action+ method returns a valid Rack application for the \Rails
-
# router to dispatch to.
-
#
-
# == Rendering Helpers
-
#
-
# <tt>ActionController::Metal</tt> by default provides no utilities for rendering
-
# views, partials, or other responses aside from explicitly calling of
-
# <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. To
-
# add the render helpers you're used to having in a normal controller, you
-
# can do the following:
-
#
-
# class HelloController < ActionController::Metal
-
# include ActionController::Rendering
-
# append_view_path "#{Rails.root}/app/views"
-
#
-
# def index
-
# render "hello/index"
-
# end
-
# end
-
#
-
# == Redirection Helpers
-
#
-
# To add redirection helpers to your metal controller, do the following:
-
#
-
# class HelloController < ActionController::Metal
-
# include ActionController::Redirecting
-
# include Rails.application.routes.url_helpers
-
#
-
# def index
-
# redirect_to root_url
-
# end
-
# end
-
#
-
# == Other Helpers
-
#
-
# You can refer to the modules included in <tt>ActionController::Base</tt> to see
-
# other features you can bring into your metal controller.
-
#
-
1
class Metal < AbstractController::Base
-
1
abstract!
-
-
1
attr_internal_writer :env
-
-
1
def env
-
5258
@_env ||= {}
-
end
-
-
# Returns the last part of the controller's name, underscored, without the ending
-
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
-
# Namespaces are left out, so Admin::PostsController returns <tt>posts</tt> as well.
-
#
-
# ==== Returns
-
# * <tt>string</tt>
-
1
def self.controller_name
-
17
@controller_name ||= name.demodulize.sub(/Controller$/, '').underscore
-
end
-
-
# Delegates to the class' <tt>controller_name</tt>
-
1
def controller_name
-
7
self.class.controller_name
-
end
-
-
# The details below can be overridden to support a specific
-
# Request and Response object. The default ActionController::Base
-
# implementation includes RackDelegation, which makes a request
-
# and response object available. You might wish to control the
-
# environment and response manually for performance reasons.
-
-
1
attr_internal :headers, :response, :request
-
1
delegate :session, :to => "@_request"
-
-
1
def initialize
-
2665
@_headers = {"Content-Type" => "text/html"}
-
2665
@_status = 200
-
2665
@_request = nil
-
2665
@_response = nil
-
2665
@_routes = nil
-
2665
super
-
end
-
-
1
def params
-
@_params ||= request.parameters
-
end
-
-
1
def params=(val)
-
5
@_params = val
-
end
-
-
# Basic implementations for content_type=, location=, and headers are
-
# provided to reduce the dependency on the RackDelegation module
-
# in Renderer and Redirector.
-
-
1
def content_type=(type)
-
13
headers["Content-Type"] = type.to_s
-
end
-
-
1
def content_type
-
headers["Content-Type"]
-
end
-
-
1
def location
-
headers["Location"]
-
end
-
-
1
def location=(url)
-
headers["Location"] = url
-
end
-
-
# basic url_for that can be overridden for more robust functionality
-
1
def url_for(string)
-
string
-
end
-
-
1
def status
-
38
@_status
-
end
-
-
1
def status=(status)
-
15
@_status = Rack::Utils.status_code(status)
-
end
-
-
1
def response_body=(body)
-
2709
body = [body] unless body.nil? || body.respond_to?(:each)
-
2709
super
-
end
-
-
1
def performed?
-
1347
response_body || (response && response.committed?)
-
end
-
-
1
def dispatch(name, request) #:nodoc:
-
372
@_request = request
-
372
@_env = request.env
-
372
@_env['action_controller.instance'] = self
-
372
process(name)
-
349
to_a
-
end
-
-
1
def to_a #:nodoc:
-
349
response ? response.to_a : [status, headers, response_body]
-
end
-
-
1
class_attribute :middleware_stack
-
1
self.middleware_stack = ActionController::MiddlewareStack.new
-
-
1
def self.inherited(base) # :nodoc:
-
318
base.middleware_stack = middleware_stack.dup
-
318
super
-
end
-
-
# Pushes the given Rack middleware and its arguments to the bottom of the
-
# middleware stack.
-
1
def self.use(*args, &block)
-
5
middleware_stack.use(*args, &block)
-
end
-
-
# Alias for +middleware_stack+.
-
1
def self.middleware
-
2
middleware_stack
-
end
-
-
# Makes the controller a Rack endpoint that runs the action in the given
-
# +env+'s +action_dispatch.request.path_parameters+ key.
-
1
def self.call(env)
-
145
action(env['action_dispatch.request.path_parameters'][:action]).call(env)
-
end
-
-
# Returns a Rack endpoint for the given action name.
-
1
def self.action(name, klass = ActionDispatch::Request)
-
373
middleware_stack.build(name.to_s) do |env|
-
370
new.dispatch(name, klass.new(env))
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/class/attribute'
-
-
1
module ActionController
-
1
module ConditionalGet
-
1
extend ActiveSupport::Concern
-
-
1
include RackDelegation
-
1
include Head
-
-
1
included do
-
1
class_attribute :etaggers
-
1
self.etaggers = []
-
end
-
-
1
module ClassMethods
-
# Allows you to consider additional controller-wide information when generating an etag.
-
# For example, if you serve pages tailored depending on who's logged in at the moment, you
-
# may want to add the current user id to be part of the etag to prevent authorized displaying
-
# of cached pages.
-
#
-
# class InvoicesController < ApplicationController
-
# etag { current_user.try :id }
-
#
-
# def show
-
# # Etag will differ even for the same invoice when it's viewed by a different current_user
-
# @invoice = Invoice.find(params[:id])
-
# fresh_when(@invoice)
-
# end
-
# end
-
1
def etag(&etagger)
-
5
self.etaggers += [etagger]
-
end
-
end
-
-
# Sets the etag, +last_modified+, or both on the response and renders a
-
# <tt>304 Not Modified</tt> response if the request is already fresh.
-
#
-
# === Parameters:
-
#
-
# * <tt>:etag</tt>.
-
# * <tt>:last_modified</tt>.
-
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
-
# +true+ if you want your application to be cachable by other devices (proxy caches).
-
#
-
# === Example:
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
# fresh_when(etag: @article, last_modified: @article.created_at, public: true)
-
# end
-
#
-
# This will render the show template if the request isn't sending a matching etag or
-
# If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
-
#
-
# You can also just pass a record where +last_modified+ will be set by calling
-
# +updated_at+ and the etag by passing the object itself.
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
# fresh_when(@article)
-
# end
-
#
-
# When passing a record, you can still set whether the public header:
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
# fresh_when(@article, public: true)
-
# end
-
1
def fresh_when(record_or_options, additional_options = {})
-
13
if record_or_options.is_a? Hash
-
9
options = record_or_options
-
9
options.assert_valid_keys(:etag, :last_modified, :public)
-
else
-
4
record = record_or_options
-
4
options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options)
-
end
-
-
13
response.etag = combine_etags(options[:etag]) if options[:etag]
-
13
response.last_modified = options[:last_modified] if options[:last_modified]
-
13
response.cache_control[:public] = true if options[:public]
-
-
13
head :not_modified if request.fresh?(response)
-
end
-
-
# Sets the +etag+ and/or +last_modified+ on the response and checks it against
-
# the client request. If the request doesn't match the options provided, the
-
# request is considered stale and should be generated from scratch. Otherwise,
-
# it's fresh and we don't need to generate anything and a reply of <tt>304 Not Modified</tt> is sent.
-
#
-
# === Parameters:
-
#
-
# * <tt>:etag</tt>.
-
# * <tt>:last_modified</tt>.
-
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
-
# +true+ if you want your application to be cachable by other devices (proxy caches).
-
#
-
# === Example:
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
#
-
# if stale?(etag: @article, last_modified: @article.created_at)
-
# @statistics = @article.really_expensive_call
-
# respond_to do |format|
-
# # all the supported formats
-
# end
-
# end
-
# end
-
#
-
# You can also just pass a record where +last_modified+ will be set by calling
-
# updated_at and the etag by passing the object itself.
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
#
-
# if stale?(@article)
-
# @statistics = @article.really_expensive_call
-
# respond_to do |format|
-
# # all the supported formats
-
# end
-
# end
-
# end
-
#
-
# When passing a record, you can still set whether the public header:
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
#
-
# if stale?(@article, public: true)
-
# @statistics = @article.really_expensive_call
-
# respond_to do |format|
-
# # all the supported formats
-
# end
-
# end
-
# end
-
1
def stale?(record_or_options, additional_options = {})
-
10
fresh_when(record_or_options, additional_options)
-
10
!request.fresh?(response)
-
end
-
-
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
-
# instruction, so that intermediate caches must not cache the response.
-
#
-
# expires_in 20.minutes
-
# expires_in 3.hours, public: true
-
# expires_in 3.hours, public: true, must_revalidate: true
-
#
-
# This method will overwrite an existing Cache-Control header.
-
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
-
#
-
# The method will also ensure a HTTP Date header for client compatibility.
-
1
def expires_in(seconds, options = {})
-
7
response.cache_control.merge!(
-
:max_age => seconds,
-
:public => options.delete(:public),
-
:must_revalidate => options.delete(:must_revalidate)
-
)
-
7
options.delete(:private)
-
-
9
response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
-
7
response.date = Time.now unless response.date?
-
end
-
-
# Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should
-
# occur by the browser or intermediate caches (like caching proxy servers).
-
1
def expires_now
-
2
response.cache_control.replace(:no_cache => true)
-
end
-
-
1
private
-
1
def combine_etags(etag)
-
23
[ etag, *etaggers.map { |etagger| instance_exec(&etagger) }.compact ]
-
end
-
end
-
end
-
1
module ActionController #:nodoc:
-
1
module Cookies
-
1
extend ActiveSupport::Concern
-
-
1
include RackDelegation
-
-
1
included do
-
1
helper_method :cookies
-
end
-
-
1
private
-
1
def cookies
-
70
request.cookie_jar
-
end
-
end
-
end
-
1
require 'action_controller/metal/exceptions'
-
-
1
module ActionController #:nodoc:
-
# Methods for sending arbitrary data and for streaming files to the browser,
-
# instead of rendering.
-
1
module DataStreaming
-
1
extend ActiveSupport::Concern
-
-
1
include ActionController::Rendering
-
-
1
DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc:
-
1
DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc:
-
-
1
protected
-
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
-
# via the Rack::Sendfile middleware. The header to use is set via
-
# +config.action_dispatch.x_sendfile_header+.
-
# Your server can also configure this for you by setting the X-Sendfile-Type header.
-
#
-
# Be careful to sanitize the path parameter if it is coming from a web
-
# page. <tt>send_file(params[:path])</tt> allows a malicious user to
-
# download any file on your server.
-
#
-
# Options:
-
# * <tt>:filename</tt> - suggests a filename for the browser to use.
-
# Defaults to <tt>File.basename(path)</tt>.
-
# * <tt>:type</tt> - specifies an HTTP content type.
-
# You can specify either a string or a symbol for a registered type register with
-
# <tt>Mime::Type.register</tt>, for example :json
-
# If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
-
# If no content type is registered for the extension, default type 'application/octet-stream' will be used.
-
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
-
# Valid values are 'inline' and 'attachment' (default).
-
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
-
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
-
# the URL, which is necessary for i18n filenames on certain browsers
-
# (setting <tt>:filename</tt> overrides this option).
-
#
-
# The default Content-Type and Content-Disposition headers are
-
# set to download arbitrary binary files in as many browsers as
-
# possible. IE versions 4, 5, 5.5, and 6 are all known to have
-
# a variety of quirks (especially when downloading over SSL).
-
#
-
# Simple download:
-
#
-
# send_file '/path/to.zip'
-
#
-
# Show a JPEG in the browser:
-
#
-
# send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
-
#
-
# Show a 404 page in the browser:
-
#
-
# send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404
-
#
-
# Read about the other Content-* HTTP headers if you'd like to
-
# provide the user with more information (such as Content-Description) in
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
-
#
-
# Also be aware that the document may be cached by proxies and browsers.
-
# The Pragma and Cache-Control headers declare how the file may be cached
-
# by intermediaries. They default to require clients to validate with
-
# the server before releasing cached responses. See
-
# http://www.mnot.net/cache_docs/ for an overview of web caching and
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
-
# for the Cache-Control header spec.
-
1
def send_file(path, options = {}) #:doc:
-
8
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
-
-
8
options[:filename] ||= File.basename(path) unless options[:url_based_filename]
-
8
send_file_headers! options
-
-
8
self.status = options[:status] || 200
-
8
self.content_type = options[:content_type] if options.key?(:content_type)
-
8
self.response_body = FileBody.new(path)
-
end
-
-
# Avoid having to pass an open file handle as the response body.
-
# Rack::Sendfile will usually intercept the response and uses
-
# the path directly, so there is no reason to open the file.
-
1
class FileBody #:nodoc:
-
1
attr_reader :to_path
-
-
1
def initialize(path)
-
8
@to_path = path
-
end
-
-
# Stream the file's contents if Rack::Sendfile isn't present.
-
1
def each
-
2
File.open(to_path, 'rb') do |file|
-
2
while chunk = file.read(16384)
-
2
yield chunk
-
end
-
end
-
end
-
end
-
-
# Sends the given binary data to the browser. This method is similar to
-
# <tt>render text: data</tt>, but also allows you to specify whether
-
# the browser should display the response as a file attachment (i.e. in a
-
# download dialog) or as inline data. You may also set the content type,
-
# the apparent file name, and other things.
-
#
-
# Options:
-
# * <tt>:filename</tt> - suggests a filename for the browser to use.
-
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
-
# either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
-
# If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
-
# If no content type is registered for the extension, default type 'application/octet-stream' will be used.
-
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
-
# Valid values are 'inline' and 'attachment' (default).
-
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
-
#
-
# Generic data download:
-
#
-
# send_data buffer
-
#
-
# Download a dynamically-generated tarball:
-
#
-
# send_data generate_tgz('dir'), filename: 'dir.tgz'
-
#
-
# Display an image Active Record in the browser:
-
#
-
# send_data image.data, type: image.content_type, disposition: 'inline'
-
#
-
# See +send_file+ for more information on HTTP Content-* headers and caching.
-
1
def send_data(data, options = {}) #:doc:
-
8
send_file_headers! options
-
8
render options.slice(:status, :content_type).merge(:text => data)
-
end
-
-
1
private
-
1
def send_file_headers!(options)
-
31
type_provided = options.has_key?(:type)
-
-
31
content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
-
31
raise ArgumentError, ":type option required" if content_type.nil?
-
-
31
if content_type.is_a?(Symbol)
-
2
extension = Mime[content_type]
-
2
raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
-
1
self.content_type = extension
-
else
-
29
if !type_provided && options[:filename]
-
# If type wasn't provided, try guessing from file extension.
-
17
content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type
-
end
-
29
self.content_type = content_type
-
end
-
-
30
disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
-
30
unless disposition.nil?
-
29
disposition += %(; filename="#{options[:filename]}") if options[:filename]
-
29
headers['Content-Disposition'] = disposition
-
end
-
-
30
headers['Content-Transfer-Encoding'] = 'binary'
-
-
30
response.sending_file = true
-
-
# Fix a problem with IE 6.0 on opening downloaded files:
-
# If Cache-Control: no-cache is set (which Rails does by default),
-
# IE removes the file it just downloaded from its cache immediately
-
# after it displays the "open/save" dialog, which means that if you
-
# hit "open" the file isn't there anymore when the application that
-
# is called for handling the download is run, so let's workaround that
-
30
response.cache_control[:public] ||= false
-
end
-
end
-
end
-
1
module ActionController
-
1
class ActionControllerError < StandardError #:nodoc:
-
end
-
-
1
class BadRequest < ActionControllerError #:nodoc:
-
1
attr_reader :original_exception
-
-
1
def initialize(type = nil, e = nil)
-
9
return super() unless type && e
-
-
4
super("Invalid #{type} parameters: #{e.message}")
-
4
@original_exception = e
-
4
set_backtrace e.backtrace
-
end
-
end
-
-
1
class RenderError < ActionControllerError #:nodoc:
-
end
-
-
1
class RoutingError < ActionControllerError #:nodoc:
-
1
attr_reader :failures
-
1
def initialize(message, failures=[])
-
194
super(message)
-
194
@failures = failures
-
end
-
end
-
-
1
class ActionController::UrlGenerationError < RoutingError #:nodoc:
-
end
-
-
1
class MethodNotAllowed < ActionControllerError #:nodoc:
-
1
def initialize(*allowed_methods)
-
2
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
-
end
-
end
-
-
1
class NotImplemented < MethodNotAllowed #:nodoc:
-
end
-
-
1
class UnknownController < ActionControllerError #:nodoc:
-
end
-
-
1
class MissingFile < ActionControllerError #:nodoc:
-
end
-
-
1
class SessionOverflowError < ActionControllerError #:nodoc:
-
1
DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
-
-
1
def initialize(message = nil)
-
super(message || DEFAULT_MESSAGE)
-
end
-
end
-
-
1
class UnknownHttpMethod < ActionControllerError #:nodoc:
-
end
-
-
1
class UnknownFormat < ActionControllerError #:nodoc:
-
end
-
end
-
1
module ActionController #:nodoc:
-
1
module Flash
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :_flash_types, instance_accessor: false
-
1
self._flash_types = []
-
-
1
delegate :flash, to: :request
-
1
add_flash_types(:alert, :notice)
-
end
-
-
1
module ClassMethods
-
1
def add_flash_types(*types)
-
3
types.each do |type|
-
4
next if _flash_types.include?(type)
-
-
4
define_method(type) do
-
4
request.flash[type]
-
end
-
4
helper_method type
-
-
4
_flash_types << type
-
end
-
end
-
end
-
-
1
protected
-
1
def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
-
84
self.class._flash_types.each do |flash_type|
-
312
if type = response_status_and_flash.delete(flash_type)
-
3
flash[flash_type] = type
-
end
-
end
-
-
84
if other_flashes = response_status_and_flash.delete(:flash)
-
1
flash.update(other_flashes)
-
end
-
-
84
super(options, response_status_and_flash)
-
end
-
end
-
end
-
1
module ActionController
-
# This module provides a method which will redirect browser to use HTTPS
-
# protocol. This will ensure that user's sensitive information will be
-
# transferred safely over the internet. You _should_ always force browser
-
# to use HTTPS when you're transferring sensitive information such as
-
# user authentication, account information, or credit card information.
-
#
-
# Note that if you are really concerned about your application security,
-
# you might consider using +config.force_ssl+ in your config file instead.
-
# That will ensure all the data transferred via HTTPS protocol and prevent
-
# user from getting session hijacked when accessing the site under unsecured
-
# HTTP protocol.
-
1
module ForceSSL
-
1
extend ActiveSupport::Concern
-
1
include AbstractController::Callbacks
-
-
1
module ClassMethods
-
# Force the request to this particular controller or specified actions to be
-
# under HTTPS protocol.
-
#
-
# If you need to disable this for any reason (e.g. development) then you can use
-
# an +:if+ or +:unless+ condition.
-
#
-
# class AccountsController < ApplicationController
-
# force_ssl if: :ssl_configured?
-
#
-
# def ssl_configured?
-
# !Rails.env.development?
-
# end
-
# end
-
#
-
# ==== Options
-
# * <tt>host</tt> - Redirect to a different host name
-
# * <tt>only</tt> - The callback should be run only for this action
-
# * <tt>except</tt> - The callback should be run for all actions except this action
-
# * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
-
# will be called only when it returns a true value.
-
# * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
-
# will be called only when it returns a false value.
-
1
def force_ssl(options = {})
-
6
host = options.delete(:host)
-
6
before_filter(options) do
-
9
force_ssl_redirect(host)
-
end
-
end
-
end
-
-
# Redirect the existing request to use the HTTPS protocol.
-
#
-
# ==== Parameters
-
# * <tt>host</tt> - Redirect to a different host name
-
1
def force_ssl_redirect(host = nil)
-
12
unless request.ssl?
-
11
redirect_options = {:protocol => 'https://', :status => :moved_permanently}
-
11
redirect_options.merge!(:host => host) if host
-
11
redirect_options.merge!(:params => request.query_parameters)
-
11
flash.keep if respond_to?(:flash)
-
11
redirect_to redirect_options
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
module Head
-
1
extend ActiveSupport::Concern
-
-
# Return a response that has no content (merely headers). The options
-
# argument is interpreted to be a hash of header names and values.
-
# This allows you to easily return a response that consists only of
-
# significant headers:
-
#
-
# head :created, location: person_path(@person)
-
#
-
# head :created, location: @person
-
#
-
# It can also be used to return exceptional conditions:
-
#
-
# return head(:method_not_allowed) unless request.post?
-
# return head(:bad_request) unless valid_request?
-
# render
-
1
def head(status, options = {})
-
336
options, status = status, nil if status.is_a?(Hash)
-
336
status ||= options.delete(:status) || :ok
-
336
location = options.delete(:location)
-
336
content_type = options.delete(:content_type)
-
-
336
options.each do |key, value|
-
11
headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s
-
end
-
-
336
self.status = status
-
336
self.location = url_for(location) if location
-
-
336
if include_content?(self.status)
-
302
self.content_type = content_type || (Mime[formats.first] if formats)
-
302
self.response_body = " "
-
else
-
34
headers.delete('Content-Type')
-
34
headers.delete('Content-Length')
-
34
self.response_body = ""
-
end
-
end
-
-
1
private
-
# :nodoc:
-
1
def include_content?(status)
-
336
case status
-
when 100..199
-
12
false
-
when 204, 205, 304
-
22
false
-
else
-
302
true
-
end
-
end
-
end
-
end
-
-
1
module ActionController
-
# The \Rails framework provides a large number of helpers for working with assets, dates, forms,
-
# numbers and model objects, to name a few. These helpers are available to all templates
-
# by default.
-
#
-
# In addition to using the standard template helpers provided, creating custom helpers to
-
# extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
-
# will include all helpers.
-
#
-
# In previous versions of \Rails the controller will include a helper whose
-
# name matches that of the controller, e.g., <tt>MyController</tt> will automatically
-
# include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
-
#
-
# Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
-
# controller which inherits from it.
-
#
-
# The +to_s+ method from the \Time class can be wrapped in a helper method to display a custom message if
-
# a \Time object is blank:
-
#
-
# module FormattedTimeHelper
-
# def format_time(time, format=:long, blank_message=" ")
-
# time.blank? ? blank_message : time.to_s(format)
-
# end
-
# end
-
#
-
# FormattedTimeHelper can now be included in a controller, using the +helper+ class method:
-
#
-
# class EventsController < ActionController::Base
-
# helper FormattedTimeHelper
-
# def index
-
# @events = Event.all
-
# end
-
# end
-
#
-
# Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
-
#
-
# <% @events.each do |event| -%>
-
# <p>
-
# <%= format_time(event.time, :short, "N/A") %> | <%= event.name %>
-
# </p>
-
# <% end -%>
-
#
-
# Finally, assuming we have two event instances, one which has a time and one which does not,
-
# the output might look like this:
-
#
-
# 23 Aug 11:30 | Carolina Railhawks Soccer Match
-
# N/A | Carolina Railhaws Training Workshop
-
#
-
1
module Helpers
-
1
extend ActiveSupport::Concern
-
-
2
class << self; attr_accessor :helpers_path; end
-
1
include AbstractController::Helpers
-
-
1
included do
-
1
class_attribute :helpers_path, :include_all_helpers
-
1
self.helpers_path ||= []
-
1
self.include_all_helpers = true
-
end
-
-
1
module ClassMethods
-
# Declares helper accessors for controller attributes. For example, the
-
# following adds new +name+ and <tt>name=</tt> instance methods to a
-
# controller and makes them available to the view:
-
# attr_accessor :name
-
# helper_attr :name
-
#
-
# ==== Parameters
-
# * <tt>attrs</tt> - Names of attributes to be converted into helpers.
-
1
def helper_attr(*attrs)
-
2
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
-
end
-
-
# Provides a proxy to access helpers methods from outside the view.
-
1
def helpers
-
1
@helper_proxy ||= ActionView::Base.new.extend(_helpers)
-
end
-
-
# Overwrite modules_for_helpers to accept :all as argument, which loads
-
# all helpers in helpers_path.
-
#
-
# ==== Parameters
-
# * <tt>args</tt> - A list of helpers
-
#
-
# ==== Returns
-
# * <tt>array</tt> - A normalized list of modules for the list of helpers provided.
-
1
def modules_for_helpers(args)
-
310
args += all_application_helpers if args.delete(:all)
-
310
super(args)
-
end
-
-
1
def all_helpers_from_path(path)
-
3
helpers = []
-
3
Array(path).each do |_path|
-
4
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
-
13
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
-
4
helpers += names.sort
-
end
-
3
helpers.uniq!
-
3
helpers
-
end
-
-
1
private
-
# Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
-
1
def all_application_helpers
-
3
all_helpers_from_path(helpers_path)
-
end
-
end
-
end
-
end
-
-
1
module ActionController
-
# Adds the ability to prevent public methods on a controller to be called as actions.
-
1
module HideActions
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :hidden_actions
-
1
self.hidden_actions = Set.new.freeze
-
end
-
-
1
private
-
-
# Overrides AbstractController::Base#action_method? to return false if the
-
# action name is in the list of hidden actions.
-
1
def method_for_action(action_name)
-
1527
self.class.visible_action?(action_name) && super
-
end
-
-
1
module ClassMethods
-
# Sets all of the actions passed in as hidden actions.
-
#
-
# ==== Parameters
-
# * <tt>args</tt> - A list of actions
-
1
def hide_action(*args)
-
4
self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze
-
end
-
-
1
def visible_action?(action_name)
-
1527
action_methods.include?(action_name)
-
end
-
-
# Overrides AbstractController::Base#action_methods to remove any methods
-
# that are listed as hidden methods.
-
1
def action_methods
-
2995
@action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze
-
end
-
end
-
end
-
end
-
1
require 'base64'
-
-
1
module ActionController
-
# Makes it dead easy to do HTTP Basic, Digest and Token authentication.
-
1
module HttpAuthentication
-
# Makes it dead easy to do HTTP \Basic authentication.
-
#
-
# === Simple \Basic example
-
#
-
# class PostsController < ApplicationController
-
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
-
#
-
# def index
-
# render text: "Everyone can see me!"
-
# end
-
#
-
# def edit
-
# render text: "I'm only accessible if you know the password"
-
# end
-
# end
-
#
-
# === Advanced \Basic example
-
#
-
# Here is a more advanced \Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
-
# the regular HTML interface is protected by a session approach:
-
#
-
# class ApplicationController < ActionController::Base
-
# before_filter :set_account, :authenticate
-
#
-
# protected
-
# def set_account
-
# @account = Account.find_by_url_name(request.subdomains.first)
-
# end
-
#
-
# def authenticate
-
# case request.format
-
# when Mime::XML, Mime::ATOM
-
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
-
# @current_user = user
-
# else
-
# request_http_basic_authentication
-
# end
-
# else
-
# if session_authenticated?
-
# @current_user = @account.users.find(session[:authenticated][:user_id])
-
# else
-
# redirect_to(login_url) and return false
-
# end
-
# end
-
# end
-
# end
-
#
-
# In your integration tests, you can do something like this:
-
#
-
# def test_access_granted_from_xml
-
# get(
-
# "/notes/1.xml", nil,
-
# 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
-
# )
-
#
-
# assert_equal 200, status
-
# end
-
1
module Basic
-
1
extend self
-
-
1
module ControllerMethods
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
def http_basic_authenticate_with(options = {})
-
1
before_filter(options.except(:name, :password, :realm)) do
-
2
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
-
2
name == options[:name] && password == options[:password]
-
end
-
end
-
end
-
end
-
-
1
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
-
18
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
-
end
-
-
1
def authenticate_with_http_basic(&login_procedure)
-
21
HttpAuthentication::Basic.authenticate(request, &login_procedure)
-
end
-
-
1
def request_http_basic_authentication(realm = "Application")
-
11
HttpAuthentication::Basic.authentication_request(self, realm)
-
end
-
end
-
-
1
def authenticate(request, &login_procedure)
-
21
unless request.authorization.blank?
-
20
login_procedure.call(*user_name_and_password(request))
-
end
-
end
-
-
1
def user_name_and_password(request)
-
20
decode_credentials(request).split(/:/, 2)
-
end
-
-
1
def decode_credentials(request)
-
20
::Base64.decode64(request.authorization.split(' ', 2).last || '')
-
end
-
-
1
def encode_credentials(user_name, password)
-
1
"Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
-
end
-
-
1
def authentication_request(controller, realm)
-
11
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
-
11
controller.response_body = "HTTP Basic: Access denied.\n"
-
11
controller.status = 401
-
end
-
end
-
-
# Makes it dead easy to do HTTP \Digest authentication.
-
#
-
# === Simple \Digest example
-
#
-
# require 'digest/md5'
-
# class PostsController < ApplicationController
-
# REALM = "SuperSecret"
-
# USERS = {"dhh" => "secret", #plain text password
-
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
-
#
-
# before_filter :authenticate, except: [:index]
-
#
-
# def index
-
# render text: "Everyone can see me!"
-
# end
-
#
-
# def edit
-
# render text: "I'm only accessible if you know the password"
-
# end
-
#
-
# private
-
# def authenticate
-
# authenticate_or_request_with_http_digest(REALM) do |username|
-
# USERS[username]
-
# end
-
# end
-
# end
-
#
-
# === Notes
-
#
-
# The +authenticate_or_request_with_http_digest+ block must return the user's password
-
# or the ha1 digest hash so the framework can appropriately hash to check the user's
-
# credentials. Returning +nil+ will cause authentication to fail.
-
#
-
# Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
-
# the password file or database is compromised, the attacker would be able to use the ha1 hash to
-
# authenticate as the user at this +realm+, but would not have the user's password to try using at
-
# other sites.
-
#
-
# In rare instances, web servers or front proxies strip authorization headers before
-
# they reach your application. You can debug this situation by logging all environment
-
# variables, and check for HTTP_AUTHORIZATION, amongst others.
-
1
module Digest
-
1
extend self
-
-
1
module ControllerMethods
-
1
def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
-
34
authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
-
end
-
-
# Authenticate with HTTP Digest, returns true or false
-
1
def authenticate_with_http_digest(realm = "Application", &password_procedure)
-
50
HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
-
end
-
-
# Render output including the HTTP Digest authentication header
-
1
def request_http_digest_authentication(realm = "Application", message = nil)
-
35
HttpAuthentication::Digest.authentication_request(self, realm, message)
-
end
-
end
-
-
# Returns false on a valid response, true otherwise
-
1
def authenticate(request, realm, &password_procedure)
-
50
request.authorization && validate_digest_response(request, realm, &password_procedure)
-
end
-
-
# Returns false unless the request credentials response value matches the expected value.
-
# First try the password as a ha1 digest password. If this fails, then try it as a plain
-
# text password.
-
1
def validate_digest_response(request, realm, &password_procedure)
-
25
secret_key = secret_token(request)
-
25
credentials = decode_credentials_header(request)
-
25
valid_nonce = validate_nonce(secret_key, request, credentials[:nonce])
-
-
25
if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque]
-
22
password = password_procedure.call(credentials[:username])
-
22
return false unless password
-
-
16
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
-
16
uri = credentials[:uri]
-
-
16
[true, false].any? do |trailing_question_mark|
-
32
[true, false].any? do |password_is_ha1|
-
63
_uri = trailing_question_mark ? uri + "?" : uri
-
63
expected = expected_response(method, _uri, credentials, password, password_is_ha1)
-
63
expected == credentials[:response]
-
end
-
end
-
end
-
end
-
-
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
-
# Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
-
# of a plain-text password.
-
1
def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
-
88
ha1 = password_is_ha1 ? password : ha1(credentials, password)
-
88
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
-
88
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
-
end
-
-
1
def ha1(credentials, password)
-
55
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
-
end
-
-
1
def encode_credentials(http_method, credentials, password, password_is_ha1)
-
25
credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
-
579
"Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ')
-
end
-
-
1
def decode_credentials_header(request)
-
25
decode_credentials(request.authorization)
-
end
-
-
1
def decode_credentials(header)
-
51
HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair|
-
407
key, value = pair.split('=', 2)
-
407
[key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')]
-
end]
-
end
-
-
1
def authentication_header(controller, realm)
-
35
secret_key = secret_token(controller.request)
-
35
nonce = self.nonce(secret_key)
-
35
opaque = opaque(secret_key)
-
35
controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
-
end
-
-
1
def authentication_request(controller, realm, message = nil)
-
35
message ||= "HTTP Digest: Access denied.\n"
-
35
authentication_header(controller, realm)
-
35
controller.response_body = message
-
35
controller.status = 401
-
end
-
-
1
def secret_token(request)
-
60
secret = request.env["action_dispatch.secret_token"]
-
60
raise "You must set config.secret_token in your app's config" if secret.blank?
-
60
secret
-
end
-
-
# Uses an MD5 digest based on time to generate a value to be used only once.
-
#
-
# A server-specified data string which should be uniquely generated each time a 401 response is made.
-
# It is recommended that this string be base64 or hexadecimal data.
-
# Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
-
#
-
# The contents of the nonce are implementation dependent.
-
# The quality of the implementation depends on a good choice.
-
# A nonce might, for example, be constructed as the base 64 encoding of
-
#
-
# time-stamp H(time-stamp ":" ETag ":" private-key)
-
#
-
# where time-stamp is a server-generated time or other non-repeating value,
-
# ETag is the value of the HTTP ETag header associated with the requested entity,
-
# and private-key is data known only to the server.
-
# With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
-
# reject the request if it did not match the nonce from that header or
-
# if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
-
# The inclusion of the ETag prevents a replay request for an updated version of the resource.
-
# (Note: including the IP address of the client in the nonce would appear to offer the server the ability
-
# to limit the reuse of the nonce to the same client that originally got it.
-
# However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
-
# Also, IP address spoofing is not that hard.)
-
#
-
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
-
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
-
# POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
-
# of this document.
-
#
-
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
-
# key from the Rails session secret generated upon creation of project. Ensures
-
# the time cannot be modified by client.
-
1
def nonce(secret_key, time = Time.now)
-
60
t = time.to_i
-
60
hashed = [t, secret_key]
-
60
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
-
60
::Base64.strict_encode64("#{t}:#{digest}")
-
end
-
-
# Might want a shorter timeout depending on whether the request
-
# is a PATCH, PUT, or POST, and if client is browser or web service.
-
# Can be much shorter if the Stale directive is implemented. This would
-
# allow a user to use new nonce without prompting user again for their
-
# username and password.
-
1
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
-
25
t = ::Base64.decode64(value).split(":").first.to_i
-
25
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
-
end
-
-
# Opaque based on random generation - but changing each request?
-
1
def opaque(secret_key)
-
58
::Digest::MD5.hexdigest(secret_key)
-
end
-
-
end
-
-
# Makes it dead easy to do HTTP Token authentication.
-
#
-
# Simple Token example:
-
#
-
# class PostsController < ApplicationController
-
# TOKEN = "secret"
-
#
-
# before_filter :authenticate, except: [ :index ]
-
#
-
# def index
-
# render text: "Everyone can see me!"
-
# end
-
#
-
# def edit
-
# render text: "I'm only accessible if you know the password"
-
# end
-
#
-
# private
-
# def authenticate
-
# authenticate_or_request_with_http_token do |token, options|
-
# token == TOKEN
-
# end
-
# end
-
# end
-
#
-
#
-
# Here is a more advanced Token example where only Atom feeds and the XML API is protected by HTTP token authentication,
-
# the regular HTML interface is protected by a session approach:
-
#
-
# class ApplicationController < ActionController::Base
-
# before_filter :set_account, :authenticate
-
#
-
# protected
-
# def set_account
-
# @account = Account.find_by_url_name(request.subdomains.first)
-
# end
-
#
-
# def authenticate
-
# case request.format
-
# when Mime::XML, Mime::ATOM
-
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
-
# @current_user = user
-
# else
-
# request_http_token_authentication
-
# end
-
# else
-
# if session_authenticated?
-
# @current_user = @account.users.find(session[:authenticated][:user_id])
-
# else
-
# redirect_to(login_url) and return false
-
# end
-
# end
-
# end
-
# end
-
#
-
#
-
# In your integration tests, you can do something like this:
-
#
-
# def test_access_granted_from_xml
-
# get(
-
# "/notes/1.xml", nil,
-
# 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
-
# )
-
#
-
# assert_equal 200, status
-
# end
-
#
-
#
-
# On shared hosts, Apache sometimes doesn't pass authentication headers to
-
# FCGI instances. If your environment matches this description and you cannot
-
# authenticate, try this rule in your Apache setup:
-
#
-
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
-
1
module Token
-
1
extend self
-
-
1
module ControllerMethods
-
1
def authenticate_or_request_with_http_token(realm = "Application", &login_procedure)
-
17
authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm)
-
end
-
-
1
def authenticate_with_http_token(&login_procedure)
-
20
Token.authenticate(self, &login_procedure)
-
end
-
-
1
def request_http_token_authentication(realm = "Application")
-
11
Token.authentication_request(self, realm)
-
end
-
end
-
-
# If token Authorization header is present, call the login
-
# procedure with the present token and options.
-
#
-
# [controller]
-
# ActionController::Base instance for the current request.
-
#
-
# [login_procedure]
-
# Proc to call if a token is present. The Proc should take two arguments:
-
#
-
# authenticate(controller) { |token, options| ... }
-
#
-
# Returns the return value of <tt>login_procedure</tt> if a
-
# token is found. Returns <tt>nil</tt> if no token is found.
-
-
1
def authenticate(controller, &login_procedure)
-
20
token, options = token_and_options(controller.request)
-
20
unless token.blank?
-
18
login_procedure.call(token, options)
-
end
-
end
-
-
# Parses the token and options out of the token authorization header. If
-
# the header looks like this:
-
# Authorization: Token token="abc", nonce="def"
-
# Then the returned token is "abc", and the options is {nonce: "def"}
-
#
-
# request - ActionDispatch::Request instance with the current headers.
-
#
-
# Returns an Array of [String, Hash] if a token is present.
-
# Returns nil if no token is found.
-
1
def token_and_options(request)
-
20
if request.authorization.to_s[/^Token (.*)/]
-
19
values = Hash[$1.split(',').map do |value|
-
24
value.strip! # remove any spaces between commas and values
-
24
key, value = value.split(/\=\"?/) # split key=value pairs
-
24
if value
-
23
value.chomp!('"') # chomp trailing " in value
-
23
value.gsub!(/\\\"/, '"') # unescape remaining quotes
-
23
[key, value]
-
end
-
end.compact]
-
19
[values.delete("token"), values.with_indifferent_access]
-
end
-
end
-
-
# Encodes the given token and options into an Authorization header value.
-
#
-
# token - String token.
-
# options - optional Hash of the options.
-
#
-
# Returns String.
-
1
def encode_credentials(token, options = {})
-
18
values = ["token=#{token.to_s.inspect}"] + options.map do |key, value|
-
5
"#{key}=#{value.to_s.inspect}"
-
end
-
18
"Token #{values * ", "}"
-
end
-
-
# Sets a WWW-Authenticate to let the client know a token is desired.
-
#
-
# controller - ActionController::Base instance for the outgoing response.
-
# realm - String realm to use in the header.
-
#
-
# Returns nothing.
-
1
def authentication_request(controller, realm)
-
11
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}")
-
11
controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
module ImplicitRender
-
1
def send_action(method, *args)
-
1424
ret = super
-
1345
default_render unless performed?
-
1345
ret
-
end
-
-
1
def default_render(*args)
-
97
render(*args)
-
end
-
-
1
def method_for_action(action_name)
-
super || if template_exists?(action_name.to_s, _prefixes)
-
34
"default_render"
-
1527
end
-
end
-
end
-
end
-
1
require 'benchmark'
-
1
require 'abstract_controller/logger'
-
-
1
module ActionController
-
# Adds instrumentation to several ends in ActionController::Base. It also provides
-
# some hooks related with process_action, this allows an ORM like Active Record
-
# and/or DataMapper to plug in ActionController and show related information.
-
#
-
# Check ActiveRecord::Railties::ControllerRuntime for an example.
-
1
module Instrumentation
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::Logger
-
1
include ActionController::RackDelegation
-
-
1
attr_internal :view_runtime
-
-
1
def process_action(*args)
-
1517
raw_payload = {
-
:controller => self.class.name,
-
:action => self.action_name,
-
:params => request.filtered_parameters,
-
:format => request.format.try(:ref),
-
:method => request.method,
-
1515
:path => (request.fullpath rescue "unknown")
-
}
-
-
1515
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
-
-
1515
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
1515
result = super
-
1448
payload[:status] = response.status
-
1448
append_info_to_payload(payload)
-
1448
result
-
end
-
end
-
-
1
def render(*args)
-
1054
render_output = nil
-
1054
self.view_runtime = cleanup_view_runtime do
-
2108
Benchmark.ms { render_output = super }
-
end
-
1012
render_output
-
end
-
-
1
def send_file(path, options={})
-
8
ActiveSupport::Notifications.instrument("send_file.action_controller",
-
options.merge(:path => path)) do
-
8
super
-
end
-
end
-
-
1
def send_data(data, options = {})
-
8
ActiveSupport::Notifications.instrument("send_data.action_controller", options) do
-
8
super
-
end
-
end
-
-
1
def redirect_to(*args)
-
84
ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
-
84
result = super
-
80
payload[:status] = response.status
-
80
payload[:location] = response.location
-
80
result
-
end
-
end
-
-
1
private
-
-
# A hook invoked everytime a before callback is halted.
-
1
def halted_callback_hook(filter)
-
77
ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
-
end
-
-
# A hook which allows you to clean up any time taken into account in
-
# views wrongly, like database querying time.
-
#
-
# def cleanup_view_runtime
-
# super - time_taken_in_something_expensive
-
# end
-
#
-
# :api: plugin
-
1
def cleanup_view_runtime #:nodoc:
-
1054
yield
-
end
-
-
# Every time after an action is processed, this method is invoked
-
# with the payload, so you can add more information.
-
# :api: plugin
-
1
def append_info_to_payload(payload) #:nodoc:
-
1448
payload[:view_runtime] = view_runtime
-
end
-
-
1
module ClassMethods
-
# A hook which allows other frameworks to log what happened during
-
# controller process action. This method should return an array
-
# with the messages to be added.
-
# :api: plugin
-
1
def log_process_action(payload) #:nodoc:
-
799
messages, view_runtime = [], payload[:view_runtime]
-
799
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
-
799
messages
-
end
-
end
-
end
-
end
-
1
require 'action_dispatch/http/response'
-
1
require 'delegate'
-
-
1
module ActionController
-
# Mix this module in to your controller, and all actions in that controller
-
# will be able to stream data to the client as it's written.
-
#
-
# class MyController < ActionController::Base
-
# include ActionController::Live
-
#
-
# def stream
-
# response.headers['Content-Type'] = 'text/event-stream'
-
# 100.times {
-
# response.stream.write "hello world\n"
-
# sleep 1
-
# }
-
# response.stream.close
-
# end
-
# end
-
#
-
# There are a few caveats with this use. You *cannot* write headers after the
-
# response has been committed (Response#committed? will return truthy).
-
# Calling +write+ or +close+ on the response stream will cause the response
-
# object to be committed. Make sure all headers are set before calling write
-
# or close on your stream.
-
#
-
# You *must* call close on your stream when you're finished, otherwise the
-
# socket may be left open forever.
-
#
-
# The final caveat is that your actions are executed in a separate thread than
-
# the main thread. Make sure your actions are thread safe, and this shouldn't
-
# be a problem (don't share state across threads, etc).
-
1
module Live
-
1
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
-
1
def initialize(response)
-
21
super(response, SizedQueue.new(10))
-
end
-
-
1
def write(string)
-
13
unless @response.committed?
-
10
@response.headers["Cache-Control"] = "no-cache"
-
10
@response.headers.delete "Content-Length"
-
end
-
-
13
super
-
end
-
-
1
def each
-
5
while str = @buf.pop
-
7
yield str
-
end
-
end
-
-
1
def close
-
8
super
-
8
@buf.push nil
-
end
-
end
-
-
1
class Response < ActionDispatch::Response #:nodoc: all
-
1
class Header < DelegateClass(Hash)
-
1
def initialize(response, header)
-
20
@response = response
-
20
super(header)
-
end
-
-
1
def []=(k,v)
-
16
if @response.committed?
-
2
raise ActionDispatch::IllegalStateError, 'header already sent'
-
end
-
-
14
super
-
end
-
-
1
def merge(other)
-
1
self.class.new @response, __getobj__.merge(other)
-
end
-
-
1
def to_hash
-
4
__getobj__.dup
-
end
-
end
-
-
1
def commit!
-
26
headers.freeze
-
26
super
-
end
-
-
1
private
-
-
1
def build_buffer(response, body)
-
21
buf = Live::Buffer.new response
-
23
body.each { |part| buf.write part }
-
21
buf
-
end
-
-
1
def merge_default_headers(original, default)
-
19
Header.new self, super
-
end
-
end
-
-
1
def process(name)
-
5
t1 = Thread.current
-
284
locals = t1.keys.map { |key| [key, t1[key]] }
-
-
# This processes the action in a child thread. It lets us return the
-
# response code and headers back up the rack stack, and still process
-
# the body in parallel with sending data to the client
-
5
Thread.new {
-
5
t2 = Thread.current
-
5
t2.abort_on_exception = true
-
-
# Since we're processing the view in a different thread, copy the
-
# thread locals from the main thread to the child thread. :'(
-
284
locals.each { |k,v| t2[k] = v }
-
-
5
begin
-
5
super(name)
-
ensure
-
5
@_response.commit!
-
end
-
}
-
-
5
@_response.await_commit
-
end
-
-
1
def response_body=(body)
-
4
super
-
4
response.stream.close if response
-
end
-
-
1
def set_response!(request)
-
1
if request.env["HTTP_VERSION"] == "HTTP/1.0"
-
super
-
else
-
1
@_response = Live::Response.new
-
1
@_response.request = request
-
end
-
end
-
end
-
end
-
1
require 'abstract_controller/collector'
-
-
1
module ActionController #:nodoc:
-
1
module MimeResponds
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :responder, :mimes_for_respond_to
-
1
self.responder = ActionController::Responder
-
1
clear_respond_to
-
end
-
-
1
module ClassMethods
-
# Defines mime types that are rendered by default when invoking
-
# <tt>respond_with</tt>.
-
#
-
# respond_to :html, :xml, :json
-
#
-
# Specifies that all actions in the controller respond to requests
-
# for <tt>:html</tt>, <tt>:xml</tt> and <tt>:json</tt>.
-
#
-
# To specify on per-action basis, use <tt>:only</tt> and
-
# <tt>:except</tt> with an array of actions or a single action:
-
#
-
# respond_to :html
-
# respond_to :xml, :json, except: [ :edit ]
-
#
-
# This specifies that all actions respond to <tt>:html</tt>
-
# and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
-
# <tt>:json</tt>.
-
#
-
# respond_to :json, only: :create
-
#
-
# This specifies that the <tt>:create</tt> action and no other responds
-
# to <tt>:json</tt>.
-
1
def respond_to(*mimes)
-
6
options = mimes.extract_options!
-
-
6
only_actions = Array(options.delete(:only)).map(&:to_s)
-
6
except_actions = Array(options.delete(:except)).map(&:to_s)
-
-
6
new = mimes_for_respond_to.dup
-
6
mimes.each do |mime|
-
9
mime = mime.to_sym
-
9
new[mime] = {}
-
9
new[mime][:only] = only_actions unless only_actions.empty?
-
9
new[mime][:except] = except_actions unless except_actions.empty?
-
end
-
6
self.mimes_for_respond_to = new.freeze
-
end
-
-
# Clear all mime types in <tt>respond_to</tt>.
-
#
-
1
def clear_respond_to
-
3
self.mimes_for_respond_to = Hash.new.freeze
-
end
-
end
-
-
# Without web-service support, an action which collects the data for displaying a list of people
-
# might look something like this:
-
#
-
# def index
-
# @people = Person.all
-
# end
-
#
-
# Here's the same action, with web-service support baked in:
-
#
-
# def index
-
# @people = Person.all
-
#
-
# respond_to do |format|
-
# format.html
-
# format.xml { render xml: @people }
-
# end
-
# end
-
#
-
# What that says is, "if the client wants HTML in response to this action, just respond as we
-
# would have before, but if the client wants XML, return them the list of people in XML format."
-
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
-
#
-
# Supposing you have an action that adds a new person, optionally creating their company
-
# (by name) if it does not already exist, without web-services, it might look like this:
-
#
-
# def create
-
# @company = Company.find_or_create_by_name(params[:company][:name])
-
# @person = @company.people.create(params[:person])
-
#
-
# redirect_to(person_list_url)
-
# end
-
#
-
# Here's the same action, with web-service support baked in:
-
#
-
# def create
-
# company = params[:person].delete(:company)
-
# @company = Company.find_or_create_by_name(company[:name])
-
# @person = @company.people.create(params[:person])
-
#
-
# respond_to do |format|
-
# format.html { redirect_to(person_list_url) }
-
# format.js
-
# format.xml { render xml: @person.to_xml(include: @company) }
-
# end
-
# end
-
#
-
# If the client wants HTML, we just redirect them back to the person list. If they want JavaScript,
-
# then it is an Ajax request and we render the JavaScript template associated with this action.
-
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
-
# include the person's company in the rendered XML, so you get something like this:
-
#
-
# <person>
-
# <id>...</id>
-
# ...
-
# <company>
-
# <id>...</id>
-
# <name>...</name>
-
# ...
-
# </company>
-
# </person>
-
#
-
# Note, however, the extra bit at the top of that action:
-
#
-
# company = params[:person].delete(:company)
-
# @company = Company.find_or_create_by_name(company[:name])
-
#
-
# This is because the incoming XML document (if a web-service request is in process) can only contain a
-
# single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
-
#
-
# person[name]=...&person[company][name]=...&...
-
#
-
# And, like this (xml-encoded):
-
#
-
# <person>
-
# <name>...</name>
-
# <company>
-
# <name>...</name>
-
# </company>
-
# </person>
-
#
-
# In other words, we make the request so that it operates on a single entity's person. Then, in the action,
-
# we extract the company data from the request, find or create the company, and then create the new person
-
# with the remaining data.
-
#
-
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities
-
# in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
-
# and accept Rails' defaults, life will be much easier.
-
#
-
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in
-
# config/initializers/mime_types.rb as follows.
-
#
-
# Mime::Type.register "image/jpg", :jpg
-
#
-
# Respond to also allows you to specify a common block for different formats by using any:
-
#
-
# def index
-
# @people = Person.all
-
#
-
# respond_to do |format|
-
# format.html
-
# format.any(:xml, :json) { render request.format.to_sym => @people }
-
# end
-
# end
-
#
-
# In the example above, if the format is xml, it will render:
-
#
-
# render xml: @people
-
#
-
# Or if the format is json:
-
#
-
# render json: @people
-
#
-
# Since this is a common pattern, you can use the class method respond_to
-
# with the respond_with method to have the same results:
-
#
-
# class PeopleController < ApplicationController
-
# respond_to :html, :xml, :json
-
#
-
# def index
-
# @people = Person.all
-
# respond_with(@people)
-
# end
-
# end
-
#
-
# Be sure to check the documentation of +respond_with+ and
-
# <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
-
1
def respond_to(*mimes, &block)
-
77
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
-
-
77
if collector = retrieve_collector_from_mimes(mimes, &block)
-
73
response = collector.response
-
73
response ? response.call : render({})
-
end
-
end
-
-
# For a given controller action, respond_with generates an appropriate
-
# response based on the mime-type requested by the client.
-
#
-
# If the method is called with just a resource, as in this example -
-
#
-
# class PeopleController < ApplicationController
-
# respond_to :html, :xml, :json
-
#
-
# def index
-
# @people = Person.all
-
# respond_with @people
-
# end
-
# end
-
#
-
# then the mime-type of the response is typically selected based on the
-
# request's Accept header and the set of available formats declared
-
# by previous calls to the controller's class method +respond_to+. Alternatively
-
# the mime-type can be selected by explicitly setting <tt>request.format</tt> in
-
# the controller.
-
#
-
# If an acceptable format is not identified, the application returns a
-
# '406 - not acceptable' status. Otherwise, the default response is to render
-
# a template named after the current action and the selected format,
-
# e.g. <tt>index.html.erb</tt>. If no template is available, the behavior
-
# depends on the selected format:
-
#
-
# * for an html response - if the request method is +get+, an exception
-
# is raised but for other requests such as +post+ the response
-
# depends on whether the resource has any validation errors (i.e.
-
# assuming that an attempt has been made to save the resource,
-
# e.g. by a +create+ action) -
-
# 1. If there are no errors, i.e. the resource
-
# was saved successfully, the response +redirect+'s to the resource
-
# i.e. its +show+ action.
-
# 2. If there are validation errors, the response
-
# renders a default action, which is <tt>:new</tt> for a
-
# +post+ request or <tt>:edit</tt> for +put+.
-
# Thus an example like this -
-
#
-
# respond_to :html, :xml
-
#
-
# def create
-
# @user = User.new(params[:user])
-
# flash[:notice] = 'User was successfully created.' if @user.save
-
# respond_with(@user)
-
# end
-
#
-
# is equivalent, in the absence of <tt>create.html.erb</tt>, to -
-
#
-
# def create
-
# @user = User.new(params[:user])
-
# respond_to do |format|
-
# if @user.save
-
# flash[:notice] = 'User was successfully created.'
-
# format.html { redirect_to(@user) }
-
# format.xml { render xml: @user }
-
# else
-
# format.html { render action: "new" }
-
# format.xml { render xml: @user }
-
# end
-
# end
-
# end
-
#
-
# * for a javascript request - if the template isn't found, an exception is
-
# raised.
-
# * for other requests - i.e. data formats such as xml, json, csv etc, if
-
# the resource passed to +respond_with+ responds to <code>to_<format></code>,
-
# the method attempts to render the resource in the requested format
-
# directly, e.g. for an xml request, the response is equivalent to calling
-
# <code>render xml: resource</code>.
-
#
-
# === Nested resources
-
#
-
# As outlined above, the +resources+ argument passed to +respond_with+
-
# can play two roles. It can be used to generate the redirect url
-
# for successful html requests (e.g. for +create+ actions when
-
# no template exists), while for formats other than html and javascript
-
# it is the object that gets rendered, by being converted directly to the
-
# required format (again assuming no template exists).
-
#
-
# For redirecting successful html requests, +respond_with+ also supports
-
# the use of nested resources, which are supplied in the same way as
-
# in <code>form_for</code> and <code>polymorphic_url</code>. For example -
-
#
-
# def create
-
# @project = Project.find(params[:project_id])
-
# @task = @project.comments.build(params[:task])
-
# flash[:notice] = 'Task was successfully created.' if @task.save
-
# respond_with(@project, @task)
-
# end
-
#
-
# This would cause +respond_with+ to redirect to <code>project_task_url</code>
-
# instead of <code>task_url</code>. For request formats other than html or
-
# javascript, if multiple resources are passed in this way, it is the last
-
# one specified that is rendered.
-
#
-
# === Customizing response behavior
-
#
-
# Like +respond_to+, +respond_with+ may also be called with a block that
-
# can be used to overwrite any of the default responses, e.g. -
-
#
-
# def create
-
# @user = User.new(params[:user])
-
# flash[:notice] = "User was successfully created." if @user.save
-
#
-
# respond_with(@user) do |format|
-
# format.html { render }
-
# end
-
# end
-
#
-
# The argument passed to the block is an ActionController::MimeResponds::Collector
-
# object which stores the responses for the formats defined within the
-
# block. Note that formats with responses defined explicitly in this way
-
# do not have to first be declared using the class method +respond_to+.
-
#
-
# Also, a hash passed to +respond_with+ immediately after the specified
-
# resource(s) is interpreted as a set of options relevant to all
-
# formats. Any option accepted by +render+ can be used, e.g.
-
# respond_with @people, status: 200
-
# However, note that these options are ignored after an unsuccessful attempt
-
# to save a resource, e.g. when automatically rendering <tt>:new</tt>
-
# after a post request.
-
#
-
# Two additional options are relevant specifically to +respond_with+ -
-
# 1. <tt>:location</tt> - overwrites the default redirect location used after
-
# a successful html +post+ request.
-
# 2. <tt>:action</tt> - overwrites the default render action used after an
-
# unsuccessful html +post+ request.
-
1
def respond_with(*resources, &block)
-
raise "In order to use respond_with, first you need to declare the formats your " <<
-
59
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
-
-
58
if collector = retrieve_collector_from_mimes(&block)
-
55
options = resources.size == 1 ? {} : resources.extract_options!
-
55
options[:default_response] = collector.response
-
55
(options.delete(:responder) || self.class.responder).call(self, resources, options)
-
end
-
end
-
-
1
protected
-
-
# Collect mimes declared in the class method respond_to valid for the
-
# current action.
-
1
def collect_mimes_from_class_level #:nodoc:
-
58
action = action_name.to_s
-
-
58
self.class.mimes_for_respond_to.keys.select do |mime|
-
261
config = self.class.mimes_for_respond_to[mime]
-
-
261
if config[:except]
-
50
!config[:except].include?(action)
-
elsif config[:only]
-
50
config[:only].include?(action)
-
else
-
161
true
-
end
-
end
-
end
-
-
# Returns a Collector object containing the appropriate mime-type response
-
# for the current request, based on the available responses defined by a block.
-
# In typical usage this is the block passed to +respond_with+ or +respond_to+.
-
#
-
# Sends :not_acceptable to the client and returns nil if no suitable format
-
# is available.
-
1
def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
-
135
mimes ||= collect_mimes_from_class_level
-
135
collector = Collector.new(mimes)
-
135
block.call(collector) if block_given?
-
135
format = collector.negotiate_format(request)
-
-
135
if format
-
128
self.content_type ||= format.to_s
-
128
lookup_context.formats = [format.to_sym]
-
128
lookup_context.rendered_format = lookup_context.formats.first
-
128
collector
-
else
-
7
raise ActionController::UnknownFormat
-
end
-
end
-
-
# A container for responses available from the current controller for
-
# requests for different mime-types sent to a particular action.
-
#
-
# The public controller methods +respond_with+ and +respond_to+ may be called
-
# with a block that is used to define responses to different mime-types, e.g.
-
# for +respond_to+ :
-
#
-
# respond_to do |format|
-
# format.html
-
# format.xml { render xml: @people }
-
# end
-
#
-
# In this usage, the argument passed to the block (+format+ above) is an
-
# instance of the ActionController::MimeResponds::Collector class. This
-
# object serves as a container in which available responses can be stored by
-
# calling any of the dynamically generated, mime-type-specific methods such
-
# as +html+, +xml+ etc on the Collector. Each response is represented by a
-
# corresponding block if present.
-
#
-
# A subsequent call to #negotiate_format(request) will enable the Collector
-
# to determine which specific mime-type it should respond with for the current
-
# request, with this response then being accessible by calling #response.
-
1
class Collector
-
1
include AbstractController::Collector
-
1
attr_accessor :order, :format
-
-
1
def initialize(mimes)
-
135
@order, @responses = [], {}
-
395
mimes.each { |mime| send(mime) }
-
end
-
-
1
def any(*args, &block)
-
31
if args.any?
-
9
args.each { |type| send(type, &block) }
-
else
-
28
custom(Mime::ALL, &block)
-
end
-
end
-
1
alias :all :any
-
-
1
def custom(mime_type, &block)
-
453
mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type)
-
453
@order << mime_type
-
453
@responses[mime_type] ||= block
-
end
-
-
1
def response
-
128
@responses[format] || @responses[Mime::ALL]
-
end
-
-
1
def negotiate_format(request)
-
135
@format = request.negotiate_mime(order)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'action_dispatch/http/mime_types'
-
-
1
module ActionController
-
# Wraps the parameters hash into a nested hash. This will allow clients to submit
-
# POST requests without having to specify any root elements.
-
#
-
# This functionality is enabled in +config/initializers/wrap_parameters.rb+
-
# and can be customized. If you are upgrading to \Rails 3.1, this file will
-
# need to be created for the functionality to be enabled.
-
#
-
# You could also turn it on per controller by setting the format array to
-
# a non-empty array:
-
#
-
# class UsersController < ApplicationController
-
# wrap_parameters format: [:json, :xml]
-
# end
-
#
-
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to
-
# send JSON parameters like this:
-
#
-
# {"user": {"name": "Konata"}}
-
#
-
# You can send parameters like this:
-
#
-
# {"name": "Konata"}
-
#
-
# And it will be wrapped into a nested hash with the key name matching the
-
# controller's name. For example, if you're posting to +UsersController+,
-
# your new +params+ hash will look like this:
-
#
-
# {"name" => "Konata", "user" => {"name" => "Konata"}}
-
#
-
# You can also specify the key in which the parameters should be wrapped to,
-
# and also the list of attributes it should wrap by using either +:include+ or
-
# +:exclude+ options like this:
-
#
-
# class UsersController < ApplicationController
-
# wrap_parameters :person, include: [:username, :password]
-
# end
-
#
-
# On ActiveRecord models with no +:include+ or +:exclude+ option set,
-
# it will only wrap the parameters returned by the class method
-
# <tt>attribute_names</tt>.
-
#
-
# If you're going to pass the parameters to an +ActiveModel+ object (such as
-
# <tt>User.new(params[:user])</tt>), you might consider passing the model class to
-
# the method instead. The +ParamsWrapper+ will actually try to determine the
-
# list of attribute names from the model and only wrap those attributes:
-
#
-
# class UsersController < ApplicationController
-
# wrap_parameters Person
-
# end
-
#
-
# You still could pass +:include+ and +:exclude+ to set the list of attributes
-
# you want to wrap.
-
#
-
# By default, if you don't specify the key in which the parameters would be
-
# wrapped to, +ParamsWrapper+ will actually try to determine if there's
-
# a model related to it or not. This controller, for example:
-
#
-
# class Admin::UsersController < ApplicationController
-
# end
-
#
-
# will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
-
# determine the wrapper key respectively. If both models don't exist,
-
# it will then fallback to use +user+ as the key.
-
1
module ParamsWrapper
-
1
extend ActiveSupport::Concern
-
-
1
EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
-
-
1
included do
-
1
class_attribute :_wrapper_options
-
1
self._wrapper_options = { :format => [] }
-
end
-
-
1
module ClassMethods
-
# Sets the name of the wrapper key, or the model which +ParamsWrapper+
-
# would use to determine the attribute names from.
-
#
-
# ==== Examples
-
# wrap_parameters format: :xml
-
# # enables the parameter wrapper for XML format
-
#
-
# wrap_parameters :person
-
# # wraps parameters into +params[:person]+ hash
-
#
-
# wrap_parameters Person
-
# # wraps parameters by determining the wrapper key from Person class
-
# (+person+, in this case) and the list of attribute names
-
#
-
# wrap_parameters include: [:username, :title]
-
# # wraps only +:username+ and +:title+ attributes from parameters.
-
#
-
# wrap_parameters false
-
# # disables parameters wrapping for this controller altogether.
-
#
-
# ==== Options
-
# * <tt>:format</tt> - The list of formats in which the parameters wrapper
-
# will be enabled.
-
# * <tt>:include</tt> - The list of attribute names which parameters wrapper
-
# will wrap into a nested hash.
-
# * <tt>:exclude</tt> - The list of attribute names which parameters wrapper
-
# will exclude from a nested hash.
-
1
def wrap_parameters(name_or_model_or_options, options = {})
-
12
model = nil
-
-
12
case name_or_model_or_options
-
when Hash
-
5
options = name_or_model_or_options
-
when false
-
1
options = options.merge(:format => [])
-
when Symbol, String
-
4
options = options.merge(:name => name_or_model_or_options)
-
else
-
2
model = name_or_model_or_options
-
end
-
-
12
_set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model)
-
end
-
-
# Sets the default wrapper key or model which will be used to determine
-
# wrapper key and attribute names. Will be called automatically when the
-
# module is inherited.
-
1
def inherited(klass)
-
308
if klass._wrapper_options[:format].present?
-
22
klass._set_wrapper_defaults(klass._wrapper_options.slice(:format))
-
end
-
308
super
-
end
-
-
1
protected
-
-
# Determine the wrapper model from the controller's name. By convention,
-
# this could be done by trying to find the defined model that has the
-
# same singularize name as the controller. For example, +UsersController+
-
# will try to find if the +User+ model exists.
-
#
-
# This method also does namespace lookup. Foo::Bar::UsersController will
-
# try to find Foo::Bar::User, Foo::User and finally User.
-
1
def _default_wrap_model #:nodoc:
-
32
return nil if self.anonymous?
-
29
model_name = self.name.sub(/Controller$/, '').classify
-
-
begin
-
42
if model_klass = model_name.safe_constantize
-
24
model_klass
-
else
-
18
namespaces = model_name.split("::")
-
18
namespaces.delete_at(-2)
-
18
break if namespaces.last == model_name
-
13
model_name = namespaces.join("::")
-
end
-
29
end until model_klass
-
-
29
model_klass
-
end
-
-
1
def _set_wrapper_defaults(options, model=nil)
-
34
options = options.dup
-
-
34
unless options[:include] || options[:exclude]
-
30
model ||= _default_wrap_model
-
30
if model.respond_to?(:attribute_names) && model.attribute_names.present?
-
5
options[:include] = model.attribute_names
-
end
-
end
-
-
34
unless options[:name] || self.anonymous?
-
27
model ||= _default_wrap_model
-
27
options[:name] = model ? model.to_s.demodulize.underscore :
-
controller_name.singularize
-
end
-
-
34
options[:include] = Array(options[:include]).collect(&:to_s) if options[:include]
-
34
options[:exclude] = Array(options[:exclude]).collect(&:to_s) if options[:exclude]
-
34
options[:format] = Array(options[:format])
-
-
34
self._wrapper_options = options
-
end
-
end
-
-
# Performs parameters wrapping upon the request. Will be called automatically
-
# by the metal call stack.
-
1
def process_action(*args)
-
1517
if _wrapper_enabled?
-
22
wrapped_hash = _wrap_parameters request.request_parameters
-
22
wrapped_keys = request.request_parameters.keys
-
22
wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
-
-
# This will make the wrapped hash accessible from controller and view
-
22
request.parameters.merge! wrapped_hash
-
22
request.request_parameters.merge! wrapped_hash
-
-
# This will make the wrapped hash displayed in the log file
-
22
request.filtered_parameters.merge! wrapped_filtered_hash
-
end
-
1517
super
-
end
-
-
1
private
-
-
# Returns the wrapper key which will use to stored wrapped parameters.
-
1
def _wrapper_key
-
91
_wrapper_options[:name]
-
end
-
-
# Returns the list of enabled formats.
-
1
def _wrapper_formats
-
1517
_wrapper_options[:format]
-
end
-
-
# Returns the list of parameters which will be selected for wrapped.
-
1
def _wrap_parameters(parameters)
-
44
value = if include_only = _wrapper_options[:include]
-
16
parameters.slice(*include_only)
-
else
-
28
exclude = _wrapper_options[:exclude] || []
-
28
parameters.except(*(exclude + EXCLUDE_PARAMETERS))
-
end
-
-
44
{ _wrapper_key => value }
-
end
-
-
# Checks if we should perform parameters wrapping.
-
1
def _wrapper_enabled?
-
1517
ref = request.content_mime_type.try(:ref)
-
1517
_wrapper_formats.include?(ref) && _wrapper_key && !request.request_parameters[_wrapper_key]
-
end
-
end
-
end
-
1
require 'action_dispatch/http/request'
-
1
require 'action_dispatch/http/response'
-
-
1
module ActionController
-
1
module RackDelegation
-
1
extend ActiveSupport::Concern
-
-
1
delegate :headers, :status=, :location=, :content_type=,
-
:status, :location, :content_type, :to => "@_response"
-
-
1
def dispatch(action, request)
-
347
set_response!(request)
-
347
super(action, request)
-
end
-
-
1
def response_body=(body)
-
2684
response.body = body if response
-
2684
super
-
end
-
-
1
def reset_session
-
17
@_request.reset_session
-
end
-
-
1
private
-
-
1
def set_response!(request)
-
347
@_response = ActionDispatch::Response.new
-
347
@_response.request = request
-
end
-
end
-
end
-
1
module ActionController
-
1
class RedirectBackError < AbstractController::Error #:nodoc:
-
1
DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
-
-
1
def initialize(message = nil)
-
1
super(message || DEFAULT_MESSAGE)
-
end
-
end
-
-
1
module Redirecting
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::Logger
-
1
include ActionController::RackDelegation
-
1
include ActionController::UrlFor
-
-
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
-
#
-
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
-
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
-
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
-
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
-
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
-
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
-
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
-
#
-
# redirect_to action: "show", id: 5
-
# redirect_to post
-
# redirect_to "http://www.rubyonrails.org"
-
# redirect_to "/images/screenshot.jpg"
-
# redirect_to articles_url
-
# redirect_to :back
-
# redirect_to proc { edit_post_url(@post) }
-
#
-
# The redirection happens as a "302 Moved" header unless otherwise specified.
-
#
-
# redirect_to post_url(@post), status: :found
-
# redirect_to action: 'atom', status: :moved_permanently
-
# redirect_to post_url(@post), status: 301
-
# redirect_to action: 'atom', status: 302
-
#
-
# The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an
-
# integer, or a symbol representing the downcased, underscored and symbolized description.
-
# Note that the status code must be a 3xx HTTP code, or redirection will not occur.
-
#
-
# If you are using XHR requests other than GET or POST and redirecting after the
-
# request then some browsers will follow the redirect using the original request
-
# method. This may lead to undesirable behavior such as a double DELETE. To work
-
# around this you can return a <tt>303 See Other</tt> status code which will be
-
# followed using a GET request.
-
#
-
# redirect_to posts_url, status: :see_other
-
# redirect_to action: 'index', status: 303
-
#
-
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
-
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
-
#
-
# redirect_to post_url(@post), alert: "Watch it, mister!"
-
# redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
-
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
-
# redirect_to { action: 'atom' }, alert: "Something serious happened"
-
#
-
# When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
-
# behavior for this case by rescuing ActionController::RedirectBackError.
-
1
def redirect_to(options = {}, response_status = {}) #:doc:
-
84
raise ActionControllerError.new("Cannot redirect to nil!") unless options
-
83
raise AbstractController::DoubleRenderError if response_body
-
154
logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger
-
-
81
self.status = _extract_redirect_to_status(options, response_status)
-
81
self.location = _compute_redirect_to_location(options)
-
80
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
-
end
-
-
1
private
-
1
def _extract_redirect_to_status(options, response_status)
-
81
status = if options.is_a?(Hash) && options.key?(:status)
-
12
Rack::Utils.status_code(options.delete(:status))
-
elsif response_status.key?(:status)
-
6
Rack::Utils.status_code(response_status[:status])
-
else
-
63
302
-
end
-
end
-
-
1
def _compute_redirect_to_location(options)
-
84
case options
-
# The scheme name consist of a letter followed by any combination of
-
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
-
# characters; and is terminated by a colon (":").
-
# The protocol relative scheme starts with a double slash "//"
-
when %r{^(\w[\w+.-]*:|//).*}
-
18
options
-
when String
-
16
request.protocol + request.host_with_port + options
-
when :back
-
3
raise RedirectBackError unless refer = request.headers["Referer"]
-
2
refer
-
when Proc
-
3
_compute_redirect_to_location options.call
-
else
-
44
url_for(options)
-
end.delete("\0\r\n")
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module ActionController
-
# See <tt>Renderers.add</tt>
-
1
def self.add_renderer(key, &block)
-
1
Renderers.add(key, &block)
-
end
-
-
1
module Renderers
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :_renderers
-
1
self._renderers = Set.new.freeze
-
end
-
-
1
module ClassMethods
-
1
def use_renderers(*args)
-
renderers = _renderers + args
-
self._renderers = renderers.freeze
-
end
-
1
alias use_renderer use_renderers
-
end
-
-
1
def render_to_body(options)
-
1066
_handle_render_options(options) || super
-
end
-
-
1
def _handle_render_options(options)
-
1066
_renderers.each do |name|
-
4190
if options.key?(name)
-
40
_process_options(options)
-
39
return send("_render_option_#{name}", options.delete(name), options)
-
end
-
end
-
nil
-
end
-
-
# Hash of available renderers, mapping a renderer name to its proc.
-
# Default keys are :json, :js, :xml.
-
1
RENDERERS = Set.new
-
-
# Adds a new renderer to call within controller actions.
-
# A renderer is invoked by passing its name as an option to
-
# <tt>AbstractController::Rendering#render</tt>. To create a renderer
-
# pass it a name and a block. The block takes two arguments, the first
-
# is the value paired with its key and the second is the remaining
-
# hash of options passed to +render+.
-
#
-
# Create a csv renderer:
-
#
-
# ActionController::Renderers.add :csv do |obj, options|
-
# filename = options[:filename] || 'data'
-
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
-
# send_data str, type: Mime::CSV,
-
# disposition: "attachment; filename=#{filename}.csv"
-
# end
-
#
-
# Note that we used Mime::CSV for the csv mime type as it comes with Rails.
-
# For a custom renderer, you'll need to register a mime type with
-
# <tt>Mime::Type.register</tt>.
-
#
-
# To use the csv renderer in a controller action:
-
#
-
# def show
-
# @csvable = Csvable.find(params[:id])
-
# respond_to do |format|
-
# format.html
-
# format.csv { render csv: @csvable, filename: @csvable.name }
-
# }
-
# end
-
# To use renderers and their mime types in more concise ways, see
-
# <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
-
# <tt>ActionController::MimeResponds#respond_with</tt>
-
1
def self.add(key, &block)
-
4
define_method("_render_option_#{key}", &block)
-
4
RENDERERS << key.to_sym
-
end
-
-
1
module All
-
1
extend ActiveSupport::Concern
-
1
include Renderers
-
-
1
included do
-
1
self._renderers = RENDERERS
-
end
-
end
-
-
1
add :json do |json, options|
-
16
json = json.to_json(options) unless json.kind_of?(String)
-
-
16
if options[:callback].present?
-
1
self.content_type ||= Mime::JS
-
1
"#{options[:callback]}(#{json})"
-
else
-
15
self.content_type ||= Mime::JSON
-
15
json
-
end
-
end
-
-
1
add :js do |js, options|
-
1
self.content_type ||= Mime::JS
-
1
js.respond_to?(:to_js) ? js.to_js(options) : js
-
end
-
-
1
add :xml do |xml, options|
-
21
self.content_type ||= Mime::XML
-
21
xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
-
end
-
end
-
end
-
1
module ActionController
-
1
module Rendering
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::Rendering
-
-
# Before processing, set the request formats in current controller formats.
-
1
def process_action(*) #:nodoc:
-
2880
self.formats = request.formats.map { |x| x.ref }
-
1425
super
-
end
-
-
# Check for double render errors and set the content_type after rendering.
-
1
def render(*args) #:nodoc:
-
1055
raise ::AbstractController::DoubleRenderError if response_body
-
1053
super
-
1013
self.content_type ||= Mime[lookup_context.rendered_format].to_s
-
1013
response_body
-
end
-
-
# Overwrite render_to_string because body can now be set to a rack body.
-
1
def render_to_string(*)
-
14
if self.response_body = super
-
12
string = ""
-
24
response_body.each { |r| string << r }
-
12
string
-
end
-
ensure
-
14
self.response_body = nil
-
end
-
-
1
def render_to_body(*)
-
1027
super || " "
-
end
-
-
1
private
-
-
# Normalize arguments by catching blocks and setting them on :update.
-
1
def _normalize_args(action=nil, options={}, &blk) #:nodoc:
-
1067
options = super
-
1067
options[:update] = blk if block_given?
-
1067
options
-
end
-
-
# Normalize both text and status options.
-
1
def _normalize_options(options) #:nodoc:
-
1067
if options.key?(:text) && options[:text].respond_to?(:to_text)
-
1
options[:text] = options[:text].to_text
-
end
-
-
1067
if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
-
95
options[:text] = " "
-
end
-
-
1067
if options[:status]
-
51
options[:status] = Rack::Utils.status_code(options[:status])
-
end
-
-
1067
super
-
end
-
-
# Process controller specific options, as status, content-type and location.
-
1
def _process_options(options) #:nodoc:
-
1067
status, content_type, location = options.values_at(:status, :content_type, :location)
-
-
1067
self.status = status if status
-
1067
self.content_type = content_type if content_type
-
1067
self.headers["Location"] = url_for(location) if location
-
-
1066
super
-
end
-
end
-
end
-
1
require 'rack/session/abstract/id'
-
1
require 'action_controller/metal/exceptions'
-
-
1
module ActionController #:nodoc:
-
1
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
-
end
-
-
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
-
# by including a token in the rendered html for your application. This token is
-
# stored as a random string in the session, to which an attacker does not have
-
# access. When a request reaches your application, \Rails verifies the received
-
# token with the token in the session. Only HTML and JavaScript requests are checked,
-
# so this will not protect your XML API (presumably you'll have a different
-
# authentication scheme there anyway). Also, GET requests are not protected as these
-
# should be idempotent.
-
#
-
# It's important to remember that XML or JSON requests are also affected and if
-
# you're building an API you'll need something like:
-
#
-
# class ApplicationController < ActionController::Base
-
# protect_from_forgery
-
# skip_before_filter :verify_authenticity_token, if: :json_request?
-
#
-
# protected
-
#
-
# def json_request?
-
# request.format.json?
-
# end
-
# end
-
#
-
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
-
# which checks the token and resets the session if it doesn't match what was expected.
-
# A call to this method is generated for new \Rails applications by default.
-
#
-
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
-
# value of this token must be added to every layout that renders forms by including
-
# <tt>csrf_meta_tags</tt> in the html +head+.
-
#
-
# Learn more about CSRF attacks and securing your application in the
-
# {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
-
1
module RequestForgeryProtection
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::Helpers
-
1
include AbstractController::Callbacks
-
-
1
included do
-
# Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
-
# sets it to <tt>:authenticity_token</tt> by default.
-
1
config_accessor :request_forgery_protection_token
-
1
self.request_forgery_protection_token ||= :authenticity_token
-
-
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
-
1
config_accessor :allow_forgery_protection
-
1
self.allow_forgery_protection = true if allow_forgery_protection.nil?
-
-
1
helper_method :form_authenticity_token
-
1
helper_method :protect_against_forgery?
-
end
-
-
1
module ClassMethods
-
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
-
#
-
# class FooController < ApplicationController
-
# protect_from_forgery except: :index
-
#
-
# You can disable csrf protection on controller-by-controller basis:
-
#
-
# skip_before_filter :verify_authenticity_token
-
#
-
# It can also be disabled for specific controller actions:
-
#
-
# skip_before_filter :verify_authenticity_token, except: [:create]
-
#
-
# Valid Options:
-
#
-
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
-
# * <tt>:with</tt> - Set the method to handle unverified request.
-
#
-
# Valid unverified request handling methods are:
-
# * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
-
# * <tt>:reset_session</tt> - Resets the session.
-
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
-
1
def protect_from_forgery(options = {})
-
6
include protection_method_module(options[:with] || :null_session)
-
6
self.request_forgery_protection_token ||= :authenticity_token
-
6
prepend_before_filter :verify_authenticity_token, options
-
end
-
-
1
private
-
-
1
def protection_method_module(name)
-
6
ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
-
rescue NameError
-
raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session'
-
end
-
end
-
-
1
module ProtectionMethods
-
1
module NullSession
-
1
protected
-
-
# This is the method that defines the application behavior when a request is found to be unverified.
-
1
def handle_unverified_request
-
2
request.session = NullSessionHash.new
-
2
request.env['action_dispatch.request.flash_hash'] = nil
-
2
request.env['rack.session.options'] = { skip: true }
-
2
request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
-
end
-
-
1
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
-
1
def initialize
-
2
super(nil, nil)
-
2
@loaded = true
-
end
-
-
1
def exists?
-
true
-
end
-
end
-
-
1
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
-
1
def self.build(request)
-
2
secret = request.env[ActionDispatch::Cookies::TOKEN_KEY]
-
2
host = request.host
-
2
secure = request.ssl?
-
-
2
new(secret, host, secure)
-
end
-
-
1
def write(*)
-
# nothing
-
end
-
end
-
end
-
-
1
module ResetSession
-
1
protected
-
-
1
def handle_unverified_request
-
8
reset_session
-
end
-
end
-
-
1
module Exception
-
1
protected
-
-
1
def handle_unverified_request
-
7
raise ActionController::InvalidAuthenticityToken
-
end
-
end
-
end
-
-
1
protected
-
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
-
1
def verify_authenticity_token
-
304
unless verified_request?
-
17
logger.warn "Can't verify CSRF token authenticity" if logger
-
17
handle_unverified_request
-
end
-
end
-
-
# Returns true or false if a request is verified. Checks:
-
#
-
# * is it a GET request? Gets should be safe and idempotent
-
# * Does the form_authenticity_token match the given token value from the params?
-
# * Does the X-CSRF-Token header match the form_authenticity_token
-
1
def verified_request?
-
304
!protect_against_forgery? || request.get? ||
-
form_authenticity_token == params[request_forgery_protection_token] ||
-
form_authenticity_token == request.headers['X-CSRF-Token']
-
end
-
-
# Sets the token value for the current session.
-
1
def form_authenticity_token
-
95
session[:_csrf_token] ||= SecureRandom.base64(32)
-
end
-
-
# The form's authenticity parameter. Override to provide your own.
-
1
def form_authenticity_param
-
params[request_forgery_protection_token]
-
end
-
-
1
def protect_against_forgery?
-
352
allow_forgery_protection
-
end
-
end
-
end
-
1
module ActionController #:nodoc:
-
# This module is responsible to provide `rescue_from` helpers
-
# to controllers and configure when detailed exceptions must be
-
# shown.
-
1
module Rescue
-
1
extend ActiveSupport::Concern
-
1
include ActiveSupport::Rescuable
-
-
1
def rescue_with_handler(exception)
-
if (exception.respond_to?(:original_exception) &&
-
91
(orig_exception = exception.original_exception) &&
-
handler_for_rescue(orig_exception))
-
1
exception = orig_exception
-
end
-
91
super(exception)
-
end
-
-
# Override this method if you want to customize when detailed
-
# exceptions must be shown. This method is only called when
-
# consider_all_requests_local is false. By default, it returns
-
# false, but someone may set it to `request.local?` so local
-
# requests in production still shows the detailed exception pages.
-
1
def show_detailed_exceptions?
-
77
false
-
end
-
-
1
private
-
1
def process_action(*args)
-
1515
super
-
rescue Exception => exception
-
91
request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
-
91
rescue_with_handler(exception) || raise(exception)
-
end
-
end
-
end
-
1
require 'active_support/json'
-
-
1
module ActionController #:nodoc:
-
# Responsible for exposing a resource to different mime requests,
-
# usually depending on the HTTP verb. The responder is triggered when
-
# <code>respond_with</code> is called. The simplest case to study is a GET request:
-
#
-
# class PeopleController < ApplicationController
-
# respond_to :html, :xml, :json
-
#
-
# def index
-
# @people = Person.all
-
# respond_with(@people)
-
# end
-
# end
-
#
-
# When a request comes in, for example for an XML response, three steps happen:
-
#
-
# 1) the responder searches for a template at people/index.xml;
-
#
-
# 2) if the template is not available, it will invoke <code>#to_xml</code> on the given resource;
-
#
-
# 3) if the responder does not <code>respond_to :to_xml</code>, call <code>#to_format</code> on it.
-
#
-
# === Builtin HTTP verb semantics
-
#
-
# The default \Rails responder holds semantics for each HTTP verb. Depending on the
-
# content type, verb and the resource status, it will behave differently.
-
#
-
# Using \Rails default responder, a POST request for creating an object could
-
# be written as:
-
#
-
# def create
-
# @user = User.new(params[:user])
-
# flash[:notice] = 'User was successfully created.' if @user.save
-
# respond_with(@user)
-
# end
-
#
-
# Which is exactly the same as:
-
#
-
# def create
-
# @user = User.new(params[:user])
-
#
-
# respond_to do |format|
-
# if @user.save
-
# flash[:notice] = 'User was successfully created.'
-
# format.html { redirect_to(@user) }
-
# format.xml { render xml: @user, status: :created, location: @user }
-
# else
-
# format.html { render action: "new" }
-
# format.xml { render xml: @user.errors, status: :unprocessable_entity }
-
# end
-
# end
-
# end
-
#
-
# The same happens for PATCH/PUT and DELETE requests.
-
#
-
# === Nested resources
-
#
-
# You can supply nested resources as you do in <code>form_for</code> and <code>polymorphic_url</code>.
-
# Consider the project has many tasks example. The create action for
-
# TasksController would be like:
-
#
-
# def create
-
# @project = Project.find(params[:project_id])
-
# @task = @project.tasks.build(params[:task])
-
# flash[:notice] = 'Task was successfully created.' if @task.save
-
# respond_with(@project, @task)
-
# end
-
#
-
# Giving several resources ensures that the responder will redirect to
-
# <code>project_task_url</code> instead of <code>task_url</code>.
-
#
-
# Namespaced and singleton resources require a symbol to be given, as in
-
# polymorphic urls. If a project has one manager which has many tasks, it
-
# should be invoked as:
-
#
-
# respond_with(@project, :manager, @task)
-
#
-
# Note that if you give an array, it will be treated as a collection,
-
# so the following is not equivalent:
-
#
-
# respond_with [@project, :manager, @task]
-
#
-
# === Custom options
-
#
-
# <code>respond_with</code> also allows you to pass options that are forwarded
-
# to the underlying render call. Those options are only applied for success
-
# scenarios. For instance, you can do the following in the create method above:
-
#
-
# def create
-
# @project = Project.find(params[:project_id])
-
# @task = @project.tasks.build(params[:task])
-
# flash[:notice] = 'Task was successfully created.' if @task.save
-
# respond_with(@project, @task, status: 201)
-
# end
-
#
-
# This will return status 201 if the task was saved successfully. If not,
-
# it will simply ignore the given options and return status 422 and the
-
# resource errors. To customize the failure scenario, you can pass a
-
# a block to <code>respond_with</code>:
-
#
-
# def create
-
# @project = Project.find(params[:project_id])
-
# @task = @project.tasks.build(params[:task])
-
# respond_with(@project, @task, status: 201) do |format|
-
# if @task.save
-
# flash[:notice] = 'Task was successfully created.'
-
# else
-
# format.html { render "some_special_template" }
-
# end
-
# end
-
# end
-
#
-
# Using <code>respond_with</code> with a block follows the same syntax as <code>respond_to</code>.
-
1
class Responder
-
1
attr_reader :controller, :request, :format, :resource, :resources, :options
-
-
1
DEFAULT_ACTIONS_FOR_VERBS = {
-
:post => :new,
-
:patch => :edit,
-
:put => :edit
-
}
-
-
1
def initialize(controller, resources, options={})
-
53
@controller = controller
-
53
@request = @controller.request
-
53
@format = @controller.formats.first
-
53
@resource = resources.last
-
53
@resources = resources
-
53
@options = options
-
53
@action = options.delete(:action)
-
53
@default_response = options.delete(:default_response)
-
end
-
-
1
delegate :head, :render, :redirect_to, :to => :controller
-
1
delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
-
-
# Undefine :to_json and :to_yaml since it's defined on Object
-
1
undef_method(:to_json) if method_defined?(:to_json)
-
1
undef_method(:to_yaml) if method_defined?(:to_yaml)
-
-
# Initializes a new responder an invoke the proper format. If the format is
-
# not defined, call to_format.
-
#
-
1
def self.call(*args)
-
53
new(*args).respond
-
end
-
-
# Main entry point for responder responsible to dispatch to the proper format.
-
#
-
1
def respond
-
51
method = "to_#{format}"
-
51
respond_to?(method) ? send(method) : to_format
-
end
-
-
# HTML format does not render the resource, it always attempt to render a
-
# template.
-
#
-
1
def to_html
-
17
default_render
-
rescue ActionView::MissingTemplate => e
-
13
navigation_behavior(e)
-
end
-
-
# to_js simply tries to render a template. If no template is found, raises the error.
-
1
def to_js
-
2
default_render
-
end
-
-
# All other formats follow the procedure below. First we try to render a
-
# template, if the template is not available, we verify if the resource
-
# responds to :to_format and display it.
-
#
-
1
def to_format
-
32
if get? || !has_errors? || response_overridden?
-
23
default_render
-
else
-
9
display_errors
-
end
-
rescue ActionView::MissingTemplate => e
-
16
api_behavior(e)
-
end
-
-
1
protected
-
-
# This is the common behavior for formats associated with browsing, like :html, :iphone and so forth.
-
1
def navigation_behavior(error)
-
13
if get?
-
1
raise error
-
12
elsif has_errors? && default_action
-
6
render :action => default_action
-
else
-
6
redirect_to navigation_location
-
end
-
end
-
-
# This is the common behavior for formats associated with APIs, such as :xml and :json.
-
1
def api_behavior(error)
-
16
raise error unless resourceful?
-
-
15
if get?
-
8
display resource
-
7
elsif post?
-
3
display resource, :status => :created, :location => api_location
-
else
-
4
head :no_content
-
end
-
end
-
-
# Checks whether the resource responds to the current format or not.
-
#
-
1
def resourceful?
-
16
resource.respond_to?("to_#{format}")
-
end
-
-
# Returns the resource location by retrieving it from the options or
-
# returning the resources array.
-
#
-
1
def resource_location
-
9
options[:location] || resources
-
end
-
1
alias :navigation_location :resource_location
-
1
alias :api_location :resource_location
-
-
# If a response block was given, use it, otherwise call render on
-
# controller.
-
#
-
1
def default_render
-
42
if @default_response
-
9
@default_response.call(options)
-
else
-
33
controller.default_render(options)
-
end
-
end
-
-
# Display is just a shortcut to render a resource with the current format.
-
#
-
# display @user, status: :ok
-
#
-
# For XML requests it's equivalent to:
-
#
-
# render xml: @user, status: :ok
-
#
-
# Options sent by the user are also used:
-
#
-
# respond_with(@user, status: :created)
-
# display(@user, status: :ok)
-
#
-
# Results in:
-
#
-
# render xml: @user, status: :created
-
#
-
1
def display(resource, given_options={})
-
11
controller.render given_options.merge!(options).merge!(format => resource)
-
end
-
-
1
def display_errors
-
9
controller.render format => resource_errors, :status => :unprocessable_entity
-
end
-
-
# Check whether the resource has errors.
-
#
-
1
def has_errors?
-
32
resource.respond_to?(:errors) && !resource.errors.empty?
-
end
-
-
# By default, render the <code>:edit</code> action for HTML requests with errors, unless
-
# the verb was POST.
-
#
-
1
def default_action
-
13
@action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
-
end
-
-
1
def resource_errors
-
9
respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors
-
end
-
-
1
def json_resource_errors
-
2
{:errors => resource.errors}
-
end
-
-
1
def response_overridden?
-
10
@default_response.present?
-
end
-
end
-
end
-
1
require 'rack/chunked'
-
-
1
module ActionController #:nodoc:
-
# Allows views to be streamed back to the client as they are rendered.
-
#
-
# The default way Rails renders views is by first rendering the template
-
# and then the layout. The response is sent to the client after the whole
-
# template is rendered, all queries are made, and the layout is processed.
-
#
-
# Streaming inverts the rendering flow by rendering the layout first and
-
# streaming each part of the layout as they are processed. This allows the
-
# header of the HTML (which is usually in the layout) to be streamed back
-
# to client very quickly, allowing JavaScripts and stylesheets to be loaded
-
# earlier than usual.
-
#
-
# This approach was introduced in Rails 3.1 and is still improving. Several
-
# Rack middlewares may not work and you need to be careful when streaming.
-
# Those points are going to be addressed soon.
-
#
-
# In order to use streaming, you will need to use a Ruby version that
-
# supports fibers (fibers are supported since version 1.9.2 of the main
-
# Ruby implementation).
-
#
-
# == Examples
-
#
-
# Streaming can be added to a given template easily, all you need to do is
-
# to pass the :stream option.
-
#
-
# class PostsController
-
# def index
-
# @posts = Post.scoped
-
# render stream: true
-
# end
-
# end
-
#
-
# == When to use streaming
-
#
-
# Streaming may be considered to be overkill for lightweight actions like
-
# +new+ or +edit+. The real benefit of streaming is on expensive actions
-
# that, for example, do a lot of queries on the database.
-
#
-
# In such actions, you want to delay queries execution as much as you can.
-
# For example, imagine the following +dashboard+ action:
-
#
-
# def dashboard
-
# @posts = Post.all
-
# @pages = Page.all
-
# @articles = Article.all
-
# end
-
#
-
# Most of the queries here are happening in the controller. In order to benefit
-
# from streaming you would want to rewrite it as:
-
#
-
# def dashboard
-
# # Allow lazy execution of the queries
-
# @posts = Post.scoped
-
# @pages = Page.scoped
-
# @articles = Article.scoped
-
# render stream: true
-
# end
-
#
-
# Notice that :stream only works with templates. Rendering :json
-
# or :xml with :stream won't work.
-
#
-
# == Communication between layout and template
-
#
-
# When streaming, rendering happens top-down instead of inside-out.
-
# Rails starts with the layout, and the template is rendered later,
-
# when its +yield+ is reached.
-
#
-
# This means that, if your application currently relies on instance
-
# variables set in the template to be used in the layout, they won't
-
# work once you move to streaming. The proper way to communicate
-
# between layout and template, regardless of whether you use streaming
-
# or not, is by using +content_for+, +provide+ and +yield+.
-
#
-
# Take a simple example where the layout expects the template to tell
-
# which title to use:
-
#
-
# <html>
-
# <head><title><%= yield :title %></title></head>
-
# <body><%= yield %></body>
-
# </html>
-
#
-
# You would use +content_for+ in your template to specify the title:
-
#
-
# <%= content_for :title, "Main" %>
-
# Hello
-
#
-
# And the final result would be:
-
#
-
# <html>
-
# <head><title>Main</title></head>
-
# <body>Hello</body>
-
# </html>
-
#
-
# However, if +content_for+ is called several times, the final result
-
# would have all calls concatenated. For instance, if we have the following
-
# template:
-
#
-
# <%= content_for :title, "Main" %>
-
# Hello
-
# <%= content_for :title, " page" %>
-
#
-
# The final result would be:
-
#
-
# <html>
-
# <head><title>Main page</title></head>
-
# <body>Hello</body>
-
# </html>
-
#
-
# This means that, if you have <code>yield :title</code> in your layout
-
# and you want to use streaming, you would have to render the whole template
-
# (and eventually trigger all queries) before streaming the title and all
-
# assets, which kills the purpose of streaming. For this reason Rails 3.1
-
# introduces a new helper called +provide+ that does the same as +content_for+
-
# but tells the layout to stop searching for other entries and continue rendering.
-
#
-
# For instance, the template above using +provide+ would be:
-
#
-
# <%= provide :title, "Main" %>
-
# Hello
-
# <%= content_for :title, " page" %>
-
#
-
# Giving:
-
#
-
# <html>
-
# <head><title>Main</title></head>
-
# <body>Hello</body>
-
# </html>
-
#
-
# That said, when streaming, you need to properly check your templates
-
# and choose when to use +provide+ and +content_for+.
-
#
-
# == Headers, cookies, session and flash
-
#
-
# When streaming, the HTTP headers are sent to the client right before
-
# it renders the first line. This means that, modifying headers, cookies,
-
# session or flash after the template starts rendering will not propagate
-
# to the client.
-
#
-
# == Middlewares
-
#
-
# Middlewares that need to manipulate the body won't work with streaming.
-
# You should disable those middlewares whenever streaming in development
-
# or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it
-
# needs to inject contents in the HTML body.
-
#
-
# Also <tt>Rack::Cache</tt> won't work with streaming as it does not support
-
# streaming bodies yet. Whenever streaming Cache-Control is automatically
-
# set to "no-cache".
-
#
-
# == Errors
-
#
-
# When it comes to streaming, exceptions get a bit more complicated. This
-
# happens because part of the template was already rendered and streamed to
-
# the client, making it impossible to render a whole exception page.
-
#
-
# Currently, when an exception happens in development or production, Rails
-
# will automatically stream to the client:
-
#
-
# "><script>window.location = "/500.html"</script></html>
-
#
-
# The first two characters (">) are required in case the exception happens
-
# while rendering attributes for a given tag. You can check the real cause
-
# for the exception in your logger.
-
#
-
# == Web server support
-
#
-
# Not all web servers support streaming out-of-the-box. You need to check
-
# the instructions for each of them.
-
#
-
# ==== Unicorn
-
#
-
# Unicorn supports streaming but it needs to be configured. For this, you
-
# need to create a config file as follow:
-
#
-
# # unicorn.config.rb
-
# listen 3000, tcp_nopush: false
-
#
-
# And use it on initialization:
-
#
-
# unicorn_rails --config-file unicorn.config.rb
-
#
-
# You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
-
# Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
-
#
-
# If you are using Unicorn with Nginx, you may need to tweak Nginx.
-
# Streaming should work out of the box on Rainbows.
-
#
-
# ==== Passenger
-
#
-
# To be described.
-
#
-
1
module Streaming
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::Rendering
-
-
1
protected
-
-
# Set proper cache control and transfer encoding when streaming
-
1
def _process_options(options) #:nodoc:
-
1066
super
-
1065
if options[:stream]
-
9
if env["HTTP_VERSION"] == "HTTP/1.0"
-
1
options.delete(:stream)
-
else
-
8
headers["Cache-Control"] ||= "no-cache"
-
8
headers["Transfer-Encoding"] = "chunked"
-
8
headers.delete("Content-Length")
-
end
-
end
-
end
-
-
# Call render_body if we are streaming instead of usual +render+.
-
1
def _render_template(options) #:nodoc:
-
1026
if options.delete(:stream)
-
8
Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
-
else
-
1018
super
-
end
-
end
-
end
-
end
-
1
require 'active_support/concern'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'active_support/rescuable'
-
-
1
module ActionController
-
# Raised when a required parameter is missing.
-
#
-
# params = ActionController::Parameters.new(a: {})
-
# params.fetch(:b)
-
# # => ActionController::ParameterMissing: param not found: b
-
# params.require(:a)
-
# # => ActionController::ParameterMissing: param not found: a
-
1
class ParameterMissing < KeyError
-
1
attr_reader :param # :nodoc:
-
-
1
def initialize(param) # :nodoc:
-
5
@param = param
-
5
super("param not found: #{param}")
-
end
-
end
-
-
# == Action Controller \Parameters
-
#
-
# Allows to choose which attributes should be whitelisted for mass updating
-
# and thus prevent accidentally exposing that which shouldn’t be exposed.
-
# Provides two methods for this purpose: #require and #permit. The former is
-
# used to mark parameters as required. The latter is used to set the parameter
-
# as permitted and limit which attributes should be allowed for mass updating.
-
#
-
# params = ActionController::Parameters.new({
-
# person: {
-
# name: 'Francesco',
-
# age: 22,
-
# role: 'admin'
-
# }
-
# })
-
#
-
# permitted = params.require(:person).permit(:name, :age)
-
# permitted # => {"name"=>"Francesco", "age"=>22}
-
# permitted.class # => ActionController::Parameters
-
# permitted.permitted? # => true
-
#
-
# Person.first.update_attributes!(permitted)
-
# # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
-
#
-
# It provides a +permit_all_parameters+ option that controls the top-level
-
# behaviour of new instances. If it's +true+, all the parameters will be
-
# permitted by default. The default value for +permit_all_parameters+
-
# option is +false+.
-
#
-
# params = ActionController::Parameters.new
-
# params.permitted? # => false
-
#
-
# ActionController::Parameters.permit_all_parameters = true
-
#
-
# params = ActionController::Parameters.new
-
# params.permitted? # => true
-
#
-
# <tt>ActionController::Parameters</tt> is inherited from
-
# <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
-
# that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
-
#
-
# params = ActionController::Parameters.new(key: 'value')
-
# params[:key] # => "value"
-
# params["key"] # => "value"
-
1
class Parameters < ActiveSupport::HashWithIndifferentAccess
-
1
cattr_accessor :permit_all_parameters, instance_accessor: false
-
-
# Returns a new instance of <tt>ActionController::Parameters</tt>.
-
# Also, sets the +permitted+ attribute to the default value of
-
# <tt>ActionController::Parameters.permit_all_parameters</tt>.
-
#
-
# class Person
-
# include ActiveRecord::Base
-
# end
-
#
-
# params = ActionController::Parameters.new(name: 'Francesco')
-
# params.permitted? # => false
-
# Person.new(params) # => ActiveModel::ForbiddenAttributesError
-
#
-
# ActionController::Parameters.permit_all_parameters = true
-
#
-
# params = ActionController::Parameters.new(name: 'Francesco')
-
# params.permitted? # => true
-
# Person.new(params) # => #<Person id: nil, name: "Francesco">
-
1
def initialize(attributes = nil)
-
1121
super(attributes)
-
1121
@permitted = self.class.permit_all_parameters
-
end
-
-
# Returns +true+ if the parameter is permitted, +false+ otherwise.
-
#
-
# params = ActionController::Parameters.new
-
# params.permitted? # => false
-
# params.permit!
-
# params.permitted? # => true
-
1
def permitted?
-
29
@permitted
-
end
-
-
# Sets the +permitted+ attribute to +true+. This can be used to pass
-
# mass assignment. Returns +self+.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# params = ActionController::Parameters.new(name: 'Francesco')
-
# params.permitted? # => false
-
# Person.new(params) # => ActiveModel::ForbiddenAttributesError
-
# params.permit!
-
# params.permitted? # => true
-
# Person.new(params) # => #<Person id: nil, name: "Francesco">
-
1
def permit!
-
55
each_pair do |key, value|
-
87
convert_hashes_to_parameters(key, value)
-
87
self[key].permit! if self[key].respond_to? :permit!
-
end
-
-
55
@permitted = true
-
55
self
-
end
-
-
# Ensures that a parameter is present. If it's present, returns
-
# the parameter at the given +key+, otherwise raises an
-
# <tt>ActionController::ParameterMissing</tt> error.
-
#
-
# ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
-
# # => {"name"=>"Francesco"}
-
#
-
# ActionController::Parameters.new(person: nil).require(:person)
-
# # => ActionController::ParameterMissing: param not found: person
-
#
-
# ActionController::Parameters.new(person: {}).require(:person)
-
# # => ActionController::ParameterMissing: param not found: person
-
1
def require(key)
-
7
self[key].presence || raise(ParameterMissing.new(key))
-
end
-
-
# Alias of #require.
-
1
alias :required :require
-
-
# Returns a new <tt>ActionController::Parameters</tt> instance that
-
# includes only the given +filters+ and sets the +permitted+ attribute
-
# for the object to +true+. This is useful for limiting which attributes
-
# should be allowed for mass updating.
-
#
-
# params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
-
# permitted = params.require(:user).permit(:name, :age)
-
# permitted.permitted? # => true
-
# permitted.has_key?(:name) # => true
-
# permitted.has_key?(:age) # => true
-
# permitted.has_key?(:role) # => false
-
#
-
# You can also use +permit+ on nested parameters, like:
-
#
-
# params = ActionController::Parameters.new({
-
# person: {
-
# name: 'Francesco',
-
# age: 22,
-
# pets: [{
-
# name: 'Purplish',
-
# category: 'dogs'
-
# }]
-
# }
-
# })
-
#
-
# permitted = params.permit(person: [ :name, { pets: :name } ])
-
# permitted.permitted? # => true
-
# permitted[:person][:name] # => "Francesco"
-
# permitted[:person][:age] # => nil
-
# permitted[:person][:pets][0][:name] # => "Purplish"
-
# permitted[:person][:pets][0][:category] # => nil
-
#
-
# Note that if you use +permit+ in a key that points to a hash,
-
# it won't allow all the hash. You also need to specify which
-
# attributes inside the hash should be whitelisted.
-
#
-
# params = ActionController::Parameters.new({
-
# person: {
-
# contact: {
-
# email: 'none@test.com'
-
# phone: '555-1234'
-
# }
-
# }
-
# })
-
#
-
# params.require(:person).permit(:contact)
-
# # => {}
-
#
-
# params.require(:person).permit(contact: :phone)
-
# # => {"contact"=>{"phone"=>"555-1234"}}
-
#
-
# params.require(:person).permit(contact: [ :email, :phone ])
-
# # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
-
1
def permit(*filters)
-
24
params = self.class.new
-
-
24
filters.flatten.each do |filter|
-
32
case filter
-
when Symbol, String then
-
18
if has_key?(filter)
-
15
_value = self[filter]
-
15
params[filter] = _value unless Hash === _value
-
end
-
25
keys.grep(/\A#{Regexp.escape(filter)}\(\d+[if]?\)\z/) { |key| params[key] = self[key] }
-
when Hash then
-
14
self.slice(*filter.keys).each do |key, values|
-
14
return unless values
-
-
14
key = key.to_sym
-
-
14
params[key] = each_element(values) do |value|
-
# filters are a Hash, so we expect value to be a Hash too
-
16
next if filter.is_a?(Hash) && !value.is_a?(Hash)
-
-
13
value = self.class.new(value) if !value.respond_to?(:permit)
-
-
13
value.permit(*Array.wrap(filter[key]))
-
end
-
end
-
end
-
end
-
-
24
params.permit!
-
end
-
-
# Returns a parameter for the given +key+. If not found,
-
# returns +nil+.
-
#
-
# params = ActionController::Parameters.new(person: { name: 'Francesco' })
-
# params[:person] # => {"name"=>"Francesco"}
-
# params[:none] # => nil
-
1
def [](key)
-
823
convert_hashes_to_parameters(key, super)
-
end
-
-
# Returns a parameter for the given +key+. If the +key+
-
# can't be found, there are several options: With no other arguments,
-
# it will raise an <tt>ActionController::ParameterMissing</tt> error;
-
# if more arguments are given, then that will be returned; if a block
-
# is given, then that will be run and its result returned.
-
#
-
# params = ActionController::Parameters.new(person: { name: 'Francesco' })
-
# params.fetch(:person) # => {"name"=>"Francesco"}
-
# params.fetch(:none) # => ActionController::ParameterMissing: param not found: none
-
# params.fetch(:none, 'Francesco') # => "Francesco"
-
# params.fetch(:none) { 'Francesco' } # => "Francesco"
-
1
def fetch(key, *args)
-
5
convert_hashes_to_parameters(key, super)
-
rescue KeyError
-
1
raise ActionController::ParameterMissing.new(key)
-
end
-
-
# Returns a new <tt>ActionController::Parameters</tt> instance that
-
# includes only the given +keys+. If the given +keys+
-
# don't exist, returns an empty hash.
-
#
-
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
-
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
-
# params.slice(:d) # => {}
-
1
def slice(*keys)
-
17
self.class.new(super).tap do |new_instance|
-
17
new_instance.instance_variable_set :@permitted, @permitted
-
end
-
end
-
-
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
-
# instance. +permitted+ state is kept on the duped object.
-
#
-
# params = ActionController::Parameters.new(a: 1)
-
# params.permit!
-
# params.permitted? # => true
-
# copy_params = params.dup # => {"a"=>1}
-
# copy_params.permitted? # => true
-
1
def dup
-
6
super.tap do |duplicate|
-
6
duplicate.instance_variable_set :@permitted, @permitted
-
end
-
end
-
-
1
private
-
1
def convert_hashes_to_parameters(key, value)
-
914
if value.is_a?(Parameters) || !value.is_a?(Hash)
-
872
value
-
else
-
# Convert to Parameters on first access
-
42
self[key] = self.class.new(value)
-
end
-
end
-
-
1
def each_element(object)
-
14
if object.is_a?(Array)
-
7
object.map { |el| yield el }.compact
-
22
elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ }
-
1
hash = object.class.new
-
3
object.each { |k,v| hash[k] = yield v }
-
1
hash
-
else
-
10
yield object
-
end
-
end
-
end
-
-
# == Strong \Parameters
-
#
-
# It provides an interface for protecting attributes from end-user
-
# assignment. This makes Action Controller parameters forbidden
-
# to be used in Active Model mass assignment until they have been
-
# whitelisted.
-
#
-
# In addition, parameters can be marked as required and flow through a
-
# predefined raise/rescue flow to end up as a 400 Bad Request with no
-
# effort.
-
#
-
# class PeopleController < ActionController::Base
-
# # Using "Person.create(params[:person])" would raise an
-
# # ActiveModel::ForbiddenAttributes exception because it'd
-
# # be using mass assignment without an explicit permit step.
-
# # This is the recommended form:
-
# def create
-
# Person.create(person_params)
-
# end
-
#
-
# # This will pass with flying colors as long as there's a person key in the
-
# # parameters, otherwise it'll raise an ActionController::MissingParameter
-
# # exception, which will get caught by ActionController::Base and turned
-
# # into a 400 Bad Request reply.
-
# def update
-
# redirect_to current_account.people.find(params[:id]).tap { |person|
-
# person.update_attributes!(person_params)
-
# }
-
# end
-
#
-
# private
-
# # Using a private method to encapsulate the permissible parameters is
-
# # just a good pattern since you'll be able to reuse the same permit
-
# # list between create and update. Also, you can specialize this method
-
# # with per-user checking of permissible attributes.
-
# def person_params
-
# params.require(:person).permit(:name, :age)
-
# end
-
# end
-
#
-
# In order to use <tt>accepts_nested_attribute_for</tt> with Strong \Parameters, you
-
# will need to specify which nested attributes should be whitelisted.
-
#
-
# class Person
-
# has_many :pets
-
# accepts_nested_attributes_for :pets
-
# end
-
#
-
# class PeopleController < ActionController::Base
-
# def create
-
# Person.create(person_params)
-
# end
-
#
-
# ...
-
#
-
# private
-
#
-
# def person_params
-
# # It's mandatory to specify the nested attributes that should be whitelisted.
-
# # If you use `permit` with just the key that points to the nested attributes hash,
-
# # it will return an empty hash.
-
# params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
-
# end
-
# end
-
#
-
# See ActionController::Parameters.require and ActionController::Parameters.permit
-
# for more information.
-
1
module StrongParameters
-
1
extend ActiveSupport::Concern
-
1
include ActiveSupport::Rescuable
-
-
1
included do
-
1
rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
-
3
render text: "Required parameter missing: #{parameter_missing_exception.param}", status: :bad_request
-
end
-
end
-
-
# Returns a new ActionController::Parameters object that
-
# has been instantiated with the <tt>request.parameters</tt>.
-
1
def params
-
289
@_params ||= Parameters.new(request.parameters)
-
end
-
-
# Assigns the given +value+ to the +params+ hash. If +value+
-
# is a Hash, this will create an ActionController::Parameters
-
# object that has been instantiated with the given +value+ hash.
-
1
def params=(value)
-
1927
@_params = value.is_a?(Hash) ? Parameters.new(value) : value
-
end
-
end
-
end
-
1
module ActionController
-
1
module Testing
-
1
extend ActiveSupport::Concern
-
-
1
include RackDelegation
-
-
# TODO : Rewrite tests using controller.headers= to use Rack env
-
1
def headers=(new_headers)
-
12
@_response ||= ActionDispatch::Response.new
-
12
@_response.headers.replace(new_headers)
-
end
-
-
# Behavior specific to functional tests
-
1
module Functional # :nodoc:
-
1
def set_response!(request)
-
end
-
-
1
def recycle!
-
1179
@_url_options = nil
-
1179
self.response_body = nil
-
1179
self.formats = nil
-
1179
self.params = nil
-
end
-
end
-
-
1
module ClassMethods
-
1
def before_filters
-
_process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
-
end
-
end
-
end
-
end
-
1
module ActionController
-
# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
-
# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
-
#
-
# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
-
# url options like the +host+. In order to do so, this module requires the host class
-
# to implement +env+ and +request+, which need to be a Rack-compatible.
-
#
-
# class RootUrl
-
# include ActionController::UrlFor
-
# include Rails.application.routes.url_helpers
-
#
-
# delegate :env, :request, to: :controller
-
#
-
# def initialize(controller)
-
# @controller = controller
-
# @url = root_path # named route from the application.
-
# end
-
# end
-
1
module UrlFor
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::UrlFor
-
-
1
def url_options
-
@_url_options ||= super.reverse_merge(
-
:host => request.host,
-
:port => request.optional_port,
-
:protocol => request.protocol,
-
:_recall => request.symbolized_path_parameters
-
1753
).freeze
-
-
1753
if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
-
3500
(script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
-
1747
(original_script_name = env['SCRIPT_NAME'])
-
29
@_url_options.dup.tap do |options|
-
29
if original_script_name
-
23
options[:original_script_name] = original_script_name
-
else
-
6
options[:script_name] = same_origin ? request.script_name.dup : script_name
-
end
-
29
options.freeze
-
end
-
else
-
1724
@_url_options
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
class Middleware < Metal
-
1
class ActionMiddleware
-
1
def initialize(controller, app)
-
2
@controller, @app = controller, app
-
end
-
-
1
def call(env)
-
2
request = ActionDispatch::Request.new(env)
-
2
@controller.build(@app).dispatch(:index, request)
-
end
-
end
-
-
1
class << self
-
1
alias build new
-
-
1
def new(app)
-
2
ActionMiddleware.new(self, app)
-
end
-
end
-
-
1
attr_internal :app
-
-
1
def process(action)
-
2
response = super
-
2
self.status, self.headers, self.response_body = response if response.is_a?(Array)
-
2
response
-
end
-
-
1
def initialize(app)
-
2
super()
-
2
@_app = app
-
end
-
-
1
def index
-
2
call(env)
-
end
-
end
-
end
-
1
module ActionController
-
1
module ModelNaming
-
# Converts the given object to an ActiveModel compliant one.
-
1
def convert_to_model(object)
-
141
object.respond_to?(:to_model) ? object.to_model : object
-
end
-
-
1
def model_name_from_record_or_class(record_or_class)
-
60
(record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
1
require 'action_view/record_identifier'
-
-
1
module ActionController
-
1
module RecordIdentifier
-
1
MESSAGE = 'method will no longer be included by default in controllers since Rails 4.1. ' +
-
'If you would like to use it in controllers, please include ' +
-
'ActionView::RecodIdentifier module.'
-
-
1
def dom_id(record, prefix = nil)
-
1
ActiveSupport::Deprecation.warn('dom_id ' + MESSAGE)
-
1
ActionView::RecordIdentifier.dom_id(record, prefix)
-
end
-
-
1
def dom_class(record, prefix = nil)
-
1
ActiveSupport::Deprecation.warn('dom_class ' + MESSAGE)
-
1
ActionView::RecordIdentifier.dom_class(record, prefix)
-
end
-
end
-
end
-
1
require 'rack/session/abstract/id'
-
1
require 'active_support/core_ext/object/to_query'
-
1
require 'active_support/core_ext/module/anonymous'
-
-
1
module ActionController
-
1
module TemplateAssertions
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
3
setup :setup_subscriptions
-
3
teardown :teardown_subscriptions
-
end
-
-
1
def setup_subscriptions
-
2704
@_partials = Hash.new(0)
-
2704
@_templates = Hash.new(0)
-
2704
@_layouts = Hash.new(0)
-
-
2704
ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
-
1012
path = payload[:layout]
-
1012
if path
-
94
@_layouts[path] += 1
-
94
if path =~ /^layouts\/(.*)/
-
71
@_layouts[$1] += 1
-
end
-
end
-
end
-
-
2704
ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
-
701
path = payload[:virtual_path]
-
701
next unless path
-
503
partial = path =~ /^.*\/_[^\/]*$/
-
-
503
if partial
-
136
@_partials[path] += 1
-
136
@_partials[path.split("/").last] += 1
-
end
-
-
503
@_templates[path] += 1
-
end
-
end
-
-
1
def teardown_subscriptions
-
2704
ActiveSupport::Notifications.unsubscribe("render_template.action_view")
-
2704
ActiveSupport::Notifications.unsubscribe("!render_template.action_view")
-
end
-
-
1
def process(*args)
-
1197
@_partials = Hash.new(0)
-
1197
@_templates = Hash.new(0)
-
1197
@_layouts = Hash.new(0)
-
1197
super
-
end
-
-
# Asserts that the request was rendered with the appropriate template file or partials.
-
#
-
# # assert that the "new" view template was rendered
-
# assert_template "new"
-
#
-
# # assert that the exact template "admin/posts/new" was rendered
-
# assert_template %r{\Aadmin/posts/new\Z}
-
#
-
# # assert that the layout 'admin' was rendered
-
# assert_template layout: 'admin'
-
# assert_template layout: 'layouts/admin'
-
# assert_template layout: :admin
-
#
-
# # assert that no layout was rendered
-
# assert_template layout: nil
-
# assert_template layout: false
-
#
-
# # assert that the "_customer" partial was rendered twice
-
# assert_template partial: '_customer', count: 2
-
#
-
# # assert that no partials were rendered
-
# assert_template partial: false
-
#
-
# In a view test case, you can also assert that specific locals are passed
-
# to partials:
-
#
-
# # assert that the "_customer" partial was rendered with a specific object
-
# assert_template partial: '_customer', locals: { customer: @customer }
-
1
def assert_template(options = {}, message = nil)
-
# Force body to be read in case the
-
# template is being streamed
-
57
response.body
-
-
57
case options
-
when NilClass, Regexp, String, Symbol
-
29
options = options.to_s if Symbol === options
-
29
rendered = @_templates
-
29
msg = message || sprintf("expecting <%s> but rendering with <%s>",
-
options.inspect, rendered.keys)
-
29
matches_template =
-
case options
-
when String
-
24
rendered.any? do |t, num|
-
24
options_splited = options.split(File::SEPARATOR)
-
24
t_splited = t.split(File::SEPARATOR)
-
24
t_splited.last(options_splited.size) == options_splited
-
end
-
when Regexp
-
2
rendered.any? { |t,num| t.match(options) }
-
when NilClass
-
4
rendered.blank?
-
end
-
29
assert matches_template, msg
-
when Hash
-
28
if options.key?(:layout)
-
15
expected_layout = options[:layout]
-
15
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
-
expected_layout, @_layouts.keys)
-
-
15
case expected_layout
-
when String, Symbol
-
10
assert_includes @_layouts.keys, expected_layout.to_s, msg
-
when Regexp
-
2
assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
-
when nil, false
-
4
assert(@_layouts.empty?, msg)
-
end
-
end
-
-
26
if expected_partial = options[:partial]
-
12
if expected_locals = options[:locals]
-
5
if defined?(@_rendered_views)
-
4
view = expected_partial.to_s.sub(/^_/,'')
-
4
msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
-
expected_locals,
-
@_rendered_views.locals_for(view)]
-
4
assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
-
else
-
1
warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
-
end
-
7
elsif expected_count = options[:count]
-
4
actual_count = @_partials[expected_partial]
-
4
msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
-
expected_partial, expected_count, actual_count)
-
4
assert(actual_count == expected_count.to_i, msg)
-
else
-
3
msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
-
options[:partial], @_partials.keys)
-
3
assert_includes @_partials, expected_partial, msg
-
end
-
14
elsif options.key?(:partial)
-
1
assert @_partials.empty?,
-
"Expected no partials to be rendered"
-
end
-
else
-
raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
-
end
-
end
-
end
-
-
1
class TestRequest < ActionDispatch::TestRequest #:nodoc:
-
1
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
-
1
DEFAULT_ENV.delete 'PATH_INFO'
-
-
1
def initialize(env = {})
-
4275
super
-
-
4275
self.session = TestSession.new
-
4275
self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
-
end
-
-
1
def assign_parameters(routes, controller_path, action, parameters = {})
-
1180
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
-
1179
extra_keys = routes.extra_keys(parameters)
-
1179
non_path_parameters = get? ? query_parameters : request_parameters
-
1179
parameters.each do |key, value|
-
2642
if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
-
2
value = value.map{ |v| v.duplicable? ? v.dup : v }
-
23
elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
-
2
value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
-
elsif value.frozen? && value.duplicable?
-
1
value = value.dup
-
end
-
-
2642
if extra_keys.include?(key.to_sym)
-
222
non_path_parameters[key] = value
-
else
-
2420
if value.is_a?(Array)
-
1
value = value.map(&:to_param)
-
else
-
2419
value = value.to_param
-
end
-
-
2420
path_parameters[key.to_s] = value
-
end
-
end
-
-
# Clear the combined params hash in case it was already referenced.
-
1179
@env.delete("action_dispatch.request.parameters")
-
-
1179
params = self.request_parameters.dup
-
1179
%w(controller action only_path).each do |k|
-
3537
params.delete(k)
-
3537
params.delete(k.to_sym)
-
end
-
1179
data = params.to_query
-
-
1179
@env['CONTENT_LENGTH'] = data.length.to_s
-
1179
@env['rack.input'] = StringIO.new(data)
-
end
-
-
1
def recycle!
-
1182
@formats = nil
-
26600
@env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
-
24599
@env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
-
1182
@symbolized_path_params = nil
-
1182
@method = @request_method = nil
-
1182
@fullpath = @ip = @remote_ip = @protocol = nil
-
1182
@env['action_dispatch.request.query_parameters'] = {}
-
1182
@set_cookies ||= {}
-
1191
@set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
-
1182
deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies")
-
1192
@set_cookies.reject!{ |k,v| deleted_cookies.include?(k) }
-
1182
cookie_jar.update(rack_cookies)
-
1182
cookie_jar.update(cookies)
-
1182
cookie_jar.update(@set_cookies)
-
1182
cookie_jar.recycle!
-
end
-
-
1
private
-
-
1
def default_env
-
4275
DEFAULT_ENV
-
end
-
end
-
-
1
class TestResponse < ActionDispatch::TestResponse
-
1
def recycle!
-
1176
initialize
-
end
-
end
-
-
1
class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
-
1
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
-
-
1
def initialize(session = {})
-
4280
super(nil, nil)
-
4280
replace(session.stringify_keys)
-
4280
@loaded = true
-
end
-
-
1
def exists?
-
true
-
end
-
end
-
-
# Superclass for ActionController functional tests. Functional tests allow you to
-
# test a single controller action per test method. This should not be confused with
-
# integration tests (see ActionDispatch::IntegrationTest), which are more like
-
# "stories" that can involve multiple controllers and multiple actions (i.e. multiple
-
# different HTTP requests).
-
#
-
# == Basic example
-
#
-
# Functional tests are written as follows:
-
# 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
-
# an HTTP request.
-
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
-
# the controller's HTTP response, the database contents, etc.
-
#
-
# For example:
-
#
-
# class BooksControllerTest < ActionController::TestCase
-
# def test_create
-
# # Simulate a POST response with the given HTTP parameters.
-
# post(:create, book: { title: "Love Hina" })
-
#
-
# # Assert that the controller tried to redirect us to
-
# # the created book's URI.
-
# assert_response :found
-
#
-
# # Assert that the controller really put the book in the database.
-
# assert_not_nil Book.find_by_title("Love Hina")
-
# end
-
# end
-
#
-
# You can also send a real document in the simulated HTTP request.
-
#
-
# def test_create
-
# json = {book: { title: "Love Hina" }}.to_json
-
# post :create, json
-
# end
-
#
-
# == Special instance variables
-
#
-
# ActionController::TestCase will also automatically provide the following instance
-
# variables for use in the tests:
-
#
-
# <b>@controller</b>::
-
# The controller instance that will be tested.
-
# <b>@request</b>::
-
# An ActionController::TestRequest, representing the current HTTP
-
# request. You can modify this object before sending the HTTP request. For example,
-
# you might want to set some session properties before sending a GET request.
-
# <b>@response</b>::
-
# An ActionController::TestResponse object, representing the response
-
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
-
# after calling +post+. If the various assert methods are not sufficient, then you
-
# may use this object to inspect the HTTP response in detail.
-
#
-
# (Earlier versions of \Rails required each functional test to subclass
-
# Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
-
#
-
# == Controller is automatically inferred
-
#
-
# ActionController::TestCase will automatically infer the controller under test
-
# from the test class name. If the controller cannot be inferred from the test
-
# class name, you can explicitly set it with +tests+.
-
#
-
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
-
# tests WidgetController
-
# end
-
#
-
# == \Testing controller internals
-
#
-
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
-
# can be used against. These collections are:
-
#
-
# * assigns: Instance variables assigned in the action that are available for the view.
-
# * session: Objects being saved in the session.
-
# * flash: The flash objects currently in the session.
-
# * cookies: \Cookies being sent to the user on this request.
-
#
-
# These collections can be used just like any other hash:
-
#
-
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
-
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
-
# assert flash.empty? # makes sure that there's nothing in the flash
-
#
-
# For historic reasons, the assigns hash uses string-based keys. So <tt>assigns[:person]</tt> won't work, but <tt>assigns["person"]</tt> will. To
-
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
-
# So <tt>assigns(:person)</tt> will work just like <tt>assigns["person"]</tt>, but again, <tt>assigns[:person]</tt> will not work.
-
#
-
# On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
-
#
-
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
-
# action call which can then be asserted against.
-
#
-
# == Manipulating session and cookie variables
-
#
-
# Sometimes you need to set up the session and cookie variables for a test.
-
# To do this just assign a value to the session or cookie collection:
-
#
-
# session[:key] = "value"
-
# cookies[:key] = "value"
-
#
-
# To clear the cookies for a test just clear the cookie collection:
-
#
-
# cookies.clear
-
#
-
# == \Testing named routes
-
#
-
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
-
#
-
# assert_redirected_to page_url(title: 'foo')
-
1
class TestCase < ActiveSupport::TestCase
-
-
# Use AC::TestCase for the base class when describing a controller
-
1
register_spec_type(self) do |desc|
-
20
Class === desc && desc < ActionController::Metal
-
end
-
1
register_spec_type(/Controller( ?Test)?\z/i, self)
-
-
1
module Behavior
-
1
extend ActiveSupport::Concern
-
1
include ActionDispatch::TestProcess
-
1
include ActiveSupport::Testing::ConstantLookup
-
-
1
attr_reader :response, :request
-
-
1
module ClassMethods
-
-
# Sets the controller class name. Useful if the name can't be inferred from test class.
-
# Normalizes +controller_class+ before using.
-
#
-
# tests WidgetController
-
# tests :widget
-
# tests 'widget'
-
1
def tests(controller_class)
-
56
case controller_class
-
when String, Symbol
-
2
self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
-
when Class
-
54
self.controller_class = controller_class
-
else
-
raise ArgumentError, "controller class must be a String, Symbol, or Class"
-
end
-
end
-
-
1
def controller_class=(new_class)
-
530
prepare_controller_class(new_class) if new_class
-
530
self._controller_class = new_class
-
end
-
-
1
def controller_class
-
1207
if current_controller_class = self._controller_class
-
733
current_controller_class
-
else
-
474
self.controller_class = determine_default_controller_class(name)
-
end
-
end
-
-
1
def determine_default_controller_class(name)
-
477
determine_constant_from_test_name(name) do |constant|
-
115
Class === constant && constant < ActionController::Metal
-
end
-
end
-
-
1
def prepare_controller_class(new_class)
-
88
new_class.send :include, ActionController::TestCase::RaiseActionExceptions
-
end
-
-
end
-
-
# Executes a request simulating GET HTTP method and set/volley the response
-
1
def get(action, *args)
-
898
process(action, "GET", *args)
-
end
-
-
# Executes a request simulating POST HTTP method and set/volley the response
-
1
def post(action, *args)
-
78
process(action, "POST", *args)
-
end
-
-
# Executes a request simulating PATCH HTTP method and set/volley the response
-
1
def patch(action, *args)
-
10
process(action, "PATCH", *args)
-
end
-
-
# Executes a request simulating PUT HTTP method and set/volley the response
-
1
def put(action, *args)
-
22
process(action, "PUT", *args)
-
end
-
-
# Executes a request simulating DELETE HTTP method and set/volley the response
-
1
def delete(action, *args)
-
14
process(action, "DELETE", *args)
-
end
-
-
# Executes a request simulating HEAD HTTP method and set/volley the response
-
1
def head(action, *args)
-
5
process(action, "HEAD", *args)
-
end
-
-
# Executes a request simulating OPTIONS HTTP method and set/volley the response
-
1
def options(action, *args)
-
1
process(action, "OPTIONS", *args)
-
end
-
-
1
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
-
15
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
-
15
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
-
15
__send__(request_method, action, parameters, session, flash).tap do
-
13
@request.env.delete 'HTTP_X_REQUESTED_WITH'
-
13
@request.env.delete 'HTTP_ACCEPT'
-
end
-
end
-
1
alias xhr :xml_http_request
-
-
1
def paramify_values(hash_or_array_or_value)
-
1506
case hash_or_array_or_value
-
when Hash
-
638
Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
-
when Array
-
5
hash_or_array_or_value.map {|i| paramify_values(i)}
-
when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile
-
2
hash_or_array_or_value
-
else
-
1211
hash_or_array_or_value.to_param
-
end
-
end
-
-
1
def process(action, http_method = 'GET', *args)
-
1197
check_required_ivars
-
1179
http_method, args = handle_old_process_api(http_method, args, caller)
-
-
1179
if args.first.is_a?(String) && http_method != 'HEAD'
-
2
@request.env['RAW_POST_DATA'] = args.shift
-
end
-
-
1179
parameters, session, flash = args
-
-
# Ensure that numbers and symbols passed as params are converted to
-
# proper params, as is the case when engaging rack.
-
1179
parameters = paramify_values(parameters) if html_format?(parameters)
-
-
1179
@html_document = nil
-
-
1179
unless @controller.respond_to?(:recycle!)
-
941
@controller.extend(Testing::Functional)
-
1882
@controller.class.class_eval { include Testing }
-
end
-
-
1179
@request.recycle!
-
1179
@response.recycle!
-
1179
@controller.recycle!
-
-
1179
@request.env['REQUEST_METHOD'] = http_method
-
-
1179
parameters ||= {}
-
1179
controller_class_name = @controller.class.anonymous? ?
-
"anonymous" :
-
@controller.class.name.underscore.sub(/_controller$/, '')
-
-
1179
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
-
-
1178
@request.session.update(session) if session
-
1178
@request.session["flash"] = @request.flash.update(flash || {})
-
-
1178
@controller.request = @request
-
1178
@controller.response = @response
-
-
1178
build_request_uri(action, parameters)
-
-
1178
name = @request.parameters[:action]
-
-
1178
@controller.process(name)
-
-
1125
if cookies = @request.env['action_dispatch.cookies']
-
1125
cookies.write(@response)
-
end
-
1125
@response.prepare!
-
-
1125
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
-
1125
@request.session.delete('flash') if @request.session['flash'].blank?
-
1125
@response
-
end
-
-
1
def setup_controller_request_and_response
-
1183
@request = build_request
-
1183
@response = build_response
-
1183
@response.request = @request
-
-
1183
@controller = nil unless defined? @controller
-
-
1183
if klass = self.class.controller_class
-
741
unless @controller
-
741
begin
-
741
@controller = klass.new
-
rescue
-
warn "could not construct controller #{klass}" if $VERBOSE
-
end
-
end
-
end
-
-
1183
if @controller
-
741
@controller.request = @request
-
741
@controller.params = {}
-
end
-
end
-
-
1
def build_request
-
1183
TestRequest.new
-
end
-
-
1
def build_response
-
1177
TestResponse.new
-
end
-
-
1
included do
-
1
include ActionController::TemplateAssertions
-
1
include ActionDispatch::Assertions
-
1
class_attribute :_controller_class
-
1
setup :setup_controller_request_and_response
-
end
-
-
1
private
-
1
def check_required_ivars
-
# Sanity check for required instance variables so we can give an
-
# understandable error message.
-
1197
[:@routes, :@controller, :@request, :@response].each do |iv_name|
-
4770
if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
-
18
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
-
end
-
end
-
end
-
-
1
def handle_old_process_api(http_method, args, callstack)
-
# 4.0: Remove this method.
-
1179
if http_method.is_a?(Hash)
-
1
ActiveSupport::Deprecation.warn("TestCase#process now expects the HTTP method as second argument: process(action, http_method, params, session, flash)", callstack)
-
1
args.unshift(http_method)
-
1
http_method = args.last.is_a?(String) ? args.last : "GET"
-
end
-
-
1179
[http_method, args]
-
end
-
-
1
def build_request_uri(action, parameters)
-
1178
unless @request.env["PATH_INFO"]
-
926
options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
-
926
options.update(
-
:only_path => true,
-
:action => action,
-
:relative_url_root => nil,
-
:_recall => @request.symbolized_path_parameters)
-
-
926
url, query_string = @routes.url_for(options).split("?", 2)
-
-
926
@request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
-
926
@request.env["PATH_INFO"] = url
-
926
@request.env["QUERY_STRING"] = query_string || ""
-
end
-
end
-
-
1
def html_format?(parameters)
-
1179
return true unless parameters.is_a?(Hash)
-
570
Mime.fetch(parameters[:format]) { Mime['html'] }.html?
-
end
-
end
-
-
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
-
# (skipping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
-
# rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
-
# than 0.0.0.0.
-
#
-
# The exception is stored in the exception accessor for further inspection.
-
1
module RaiseActionExceptions
-
1
def self.included(base) #:nodoc:
-
88
unless base.method_defined?(:exception) && base.method_defined?(:exception=)
-
60
base.class_eval do
-
60
attr_accessor :exception
-
60
protected :exception, :exception=
-
end
-
end
-
end
-
-
1
protected
-
1
def rescue_action_without_handler(e)
-
self.exception = e
-
-
if request.remote_addr == "0.0.0.0"
-
raise(e)
-
else
-
super(e)
-
end
-
end
-
end
-
-
1
include Behavior
-
end
-
end
-
#--
-
# Copyright (c) 2004-2012 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
1
require 'active_support'
-
1
require 'active_support/rails'
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
-
1
require 'action_pack'
-
1
require 'rack'
-
-
1
module Rack
-
1
autoload :Test, 'rack/test'
-
end
-
-
1
module ActionDispatch
-
1
extend ActiveSupport::Autoload
-
-
1
class IllegalStateError < StandardError
-
end
-
-
1
eager_autoload do
-
1
autoload_under 'http' do
-
1
autoload :Request
-
1
autoload :Response
-
end
-
end
-
-
1
autoload_under 'middleware' do
-
1
autoload :RequestId
-
1
autoload :BestStandardsSupport
-
1
autoload :Callbacks
-
1
autoload :Cookies
-
1
autoload :DebugExceptions
-
1
autoload :ExceptionWrapper
-
1
autoload :Flash
-
1
autoload :Head
-
1
autoload :ParamsParser
-
1
autoload :PublicExceptions
-
1
autoload :Reloader
-
1
autoload :RemoteIp
-
1
autoload :ShowExceptions
-
1
autoload :SSL
-
1
autoload :Static
-
end
-
-
1
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
-
1
autoload :Routing
-
-
1
module Http
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Cache
-
1
autoload :Headers
-
1
autoload :MimeNegotiation
-
1
autoload :Parameters
-
1
autoload :ParameterFilter
-
1
autoload :FilterParameters
-
1
autoload :Upload
-
1
autoload :UploadedFile, 'action_dispatch/http/upload'
-
1
autoload :URL
-
end
-
-
1
module Session
-
1
autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
-
1
autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
-
1
autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
-
1
autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
-
end
-
-
1
mattr_accessor :test_app
-
-
1
autoload_under 'testing' do
-
1
autoload :Assertions
-
1
autoload :Integration
-
1
autoload :IntegrationTest, 'action_dispatch/testing/integration'
-
1
autoload :PerformanceTest
-
1
autoload :TestProcess
-
1
autoload :TestRequest
-
1
autoload :TestResponse
-
end
-
end
-
-
1
autoload :Mime, 'action_dispatch/http/mime_type'
-
-
1
ActiveSupport.on_load(:action_view) do
-
1
ActionView::Base.default_formats ||= Mime::SET.symbols
-
1
ActionView::Template::Types.delegate_to Mime
-
end
-
-
1
module ActionDispatch
-
1
module Http
-
1
module Cache
-
1
module Request
-
-
1
HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze
-
1
HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
-
-
1
def if_modified_since
-
51
if since = env[HTTP_IF_MODIFIED_SINCE]
-
42
Time.rfc2822(since) rescue nil
-
end
-
end
-
-
1
def if_none_match
-
63
env[HTTP_IF_NONE_MATCH]
-
end
-
-
1
def if_none_match_etags
-
19
(if_none_match ? if_none_match.split(/\s*,\s*/) : []).collect do |etag|
-
32
etag.gsub(/^\"|\"$/, "")
-
end
-
end
-
-
1
def not_modified?(modified_at)
-
14
if_modified_since && modified_at && if_modified_since >= modified_at
-
end
-
-
1
def etag_matches?(etag)
-
16
if etag
-
15
etag = etag.gsub(/^\"|\"$/, "")
-
15
if_none_match_etags.include?(etag)
-
end
-
end
-
-
# Check response freshness (Last-Modified and ETag) against request
-
# If-Modified-Since and If-None-Match conditions. If both headers are
-
# supplied, both must match, or the request is not considered fresh.
-
1
def fresh?(response)
-
23
last_modified = if_modified_since
-
23
etag = if_none_match
-
-
23
return false unless last_modified || etag
-
-
18
success = true
-
18
success &&= not_modified?(response.last_modified) if last_modified
-
18
success &&= etag_matches?(response.etag) if etag
-
18
success
-
end
-
end
-
-
1
module Response
-
1
attr_reader :cache_control, :etag
-
1
alias :etag? :etag
-
-
1
def last_modified
-
14
if last = headers[LAST_MODIFIED]
-
14
Time.httpdate(last)
-
end
-
end
-
-
1
def last_modified?
-
1454
headers.include?(LAST_MODIFIED)
-
end
-
-
1
def last_modified=(utc_time)
-
11
headers[LAST_MODIFIED] = utc_time.httpdate
-
end
-
-
1
def date
-
if date_header = headers['Date']
-
Time.httpdate(date_header)
-
end
-
end
-
-
1
def date?
-
7
headers.include?('Date')
-
end
-
-
1
def date=(utc_time)
-
7
headers['Date'] = utc_time.httpdate
-
end
-
-
1
def etag=(etag)
-
15
key = ActiveSupport::Cache.expand_cache_key(etag)
-
15
@etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
-
end
-
-
1
private
-
-
1
LAST_MODIFIED = "Last-Modified".freeze
-
1
ETAG = "ETag".freeze
-
1
CACHE_CONTROL = "Cache-Control".freeze
-
1
SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
-
-
1
def cache_control_segments
-
4801
if cache_control = self[CACHE_CONTROL]
-
10
cache_control.delete(' ').split(',')
-
else
-
4791
[]
-
end
-
end
-
-
1
def cache_control_headers
-
4801
cache_control = {}
-
-
4801
cache_control_segments.each do |segment|
-
10
directive, argument = segment.split('=', 2)
-
-
10
if SPESHUL_KEYS.include? directive
-
8
key = directive.tr('-', '_')
-
8
cache_control[key.to_sym] = argument || true
-
else
-
2
cache_control[:extras] ||= []
-
2
cache_control[:extras] << segment
-
end
-
end
-
-
4801
cache_control
-
end
-
-
1
def prepare_cache_control!
-
4760
@cache_control = cache_control_headers
-
4760
@etag = self[ETAG]
-
end
-
-
1
def handle_conditional_get!
-
1469
if etag? || last_modified? || !@cache_control.empty?
-
41
set_conditional_cache_control!
-
end
-
end
-
-
1
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
-
1
NO_CACHE = "no-cache".freeze
-
1
PUBLIC = "public".freeze
-
1
PRIVATE = "private".freeze
-
1
MUST_REVALIDATE = "must-revalidate".freeze
-
-
1
def set_conditional_cache_control!
-
41
control = {}
-
41
cc_headers = cache_control_headers
-
41
if extras = cc_headers.delete(:extras)
-
1
@cache_control[:extras] ||= []
-
1
@cache_control[:extras] += extras
-
1
@cache_control[:extras].uniq!
-
end
-
-
41
control.merge! cc_headers
-
41
control.merge! @cache_control
-
-
41
if control.empty?
-
13
headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
-
28
elsif control[:no_cache]
-
2
headers[CACHE_CONTROL] = NO_CACHE
-
2
if control[:extras]
-
1
headers[CACHE_CONTROL] += ", #{control[:extras].join(', ')}"
-
end
-
else
-
26
extras = control[:extras]
-
26
max_age = control[:max_age]
-
-
26
options = []
-
26
options << "max-age=#{max_age.to_i}" if max_age
-
26
options << (control[:public] ? PUBLIC : PRIVATE)
-
26
options << MUST_REVALIDATE if control[:must_revalidate]
-
26
options.concat(extras) if extras
-
-
26
headers[CACHE_CONTROL] = options.join(", ")
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'mutex_m'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/object/duplicable'
-
-
1
module ActionDispatch
-
1
module Http
-
# Allows you to specify sensitive parameters which will be replaced from
-
# the request log by looking in the query string of the request and all
-
# subhashes of the params hash to filter. If a block is given, each key and
-
# value of the params hash and all subhashes is passed to it, the value
-
# or key can be replaced using String#replace or similar method.
-
#
-
# env["action_dispatch.parameter_filter"] = [:password]
-
# => replaces the value to all keys matching /password/i with "[FILTERED]"
-
#
-
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
-
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
-
#
-
# env["action_dispatch.parameter_filter"] = lambda do |k,v|
-
# v.reverse! if k =~ /secret/i
-
# end
-
# => reverses the value to all keys matching /secret/i
-
1
module FilterParameters
-
1
@@parameter_filter_for = {}.extend(Mutex_m)
-
-
1
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
-
1
NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
-
1
NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
-
-
1
def initialize(env)
-
10839
super
-
10839
@filtered_parameters = nil
-
10839
@filtered_env = nil
-
10839
@filtered_path = nil
-
end
-
-
# Return a hash of parameters with all sensitive data replaced.
-
1
def filtered_parameters
-
1582
@filtered_parameters ||= parameter_filter.filter(parameters)
-
end
-
-
# Return a hash of request.env with all sensitive data replaced.
-
1
def filtered_env
-
2
@filtered_env ||= env_filter.filter(@env)
-
end
-
-
# Reconstructed a path with all sensitive GET parameters replaced.
-
1
def filtered_path
-
5
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
-
end
-
-
1
protected
-
-
1
def parameter_filter
-
parameter_filter_for @env.fetch("action_dispatch.parameter_filter") {
-
1285
return NULL_PARAM_FILTER
-
1301
}
-
end
-
-
1
def env_filter
-
2
user_key = @env.fetch("action_dispatch.parameter_filter") {
-
return NULL_ENV_FILTER
-
}
-
2
parameter_filter_for(Array(user_key) + ENV_MATCH)
-
end
-
-
1
def parameter_filter_for(filters)
-
18
@@parameter_filter_for.synchronize do
-
# Do we *actually* need this cache? Constructing ParameterFilters
-
# doesn't seem too expensive.
-
18
@@parameter_filter_for[filters] ||= ParameterFilter.new(filters)
-
end
-
end
-
-
1
KV_RE = '[^&;=]+'
-
1
PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
-
1
def filtered_query_string
-
5
query_string.gsub(PAIR_RE) do |_|
-
11
parameter_filter.filter([[$1, $2]]).first.join("=")
-
end
-
end
-
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
module Http
-
1
class Headers
-
1
include Enumerable
-
-
1
def initialize(env = {})
-
34
@headers = env
-
end
-
-
1
def [](header_name)
-
33
@headers[env_name(header_name)]
-
end
-
-
2
def []=(k,v); @headers[k] = v; end
-
3
def key?(k); @headers.key? k; end
-
1
alias :include? :key?
-
-
1
def fetch(header_name, *args, &block)
-
3
@headers.fetch env_name(header_name), *args, &block
-
end
-
-
1
def each(&block)
-
1
@headers.each(&block)
-
end
-
-
1
private
-
-
# Converts a HTTP header name to an environment variable name if it is
-
# not contained within the headers hash.
-
1
def env_name(header_name)
-
36
@headers.include?(header_name) ? header_name : cgi_name(header_name)
-
end
-
-
1
def cgi_name(k)
-
34
"HTTP_#{k.upcase.gsub(/-/, '_')}"
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
-
1
module ActionDispatch
-
1
module Http
-
1
module MimeNegotiation
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
mattr_accessor :ignore_accept_header
-
1
self.ignore_accept_header = false
-
end
-
-
# The MIME type of the HTTP request, such as Mime::XML.
-
#
-
# For backward compatibility, the post \format is extracted from the
-
# X-Post-Data-Format HTTP header if present.
-
1
def content_mime_type
-
4332
@env["action_dispatch.request.content_type"] ||= begin
-
3933
if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
-
394
Mime::Type.lookup($1.strip.downcase)
-
else
-
3539
nil
-
end
-
end
-
end
-
-
1
def content_type
-
content_mime_type && content_mime_type.to_s
-
end
-
-
# Returns the accepted MIME type for the request.
-
1
def accepts
-
104
@env["action_dispatch.request.accepts"] ||= begin
-
104
header = @env['HTTP_ACCEPT'].to_s.strip
-
-
104
if header.empty?
-
4
[content_mime_type]
-
else
-
100
Mime::Type.parse(header)
-
end
-
end
-
end
-
-
# Returns the MIME type for the \format used in the request.
-
#
-
# GET /posts/5.xml | request.format => Mime::XML
-
# GET /posts/5.xhtml | request.format => Mime::HTML
-
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first
-
#
-
1
def format(view_path = [])
-
1527
formats.first
-
end
-
-
1
def formats
-
3126
@env["action_dispatch.request.formats"] ||=
-
if parameters[:format]
-
48
Array(Mime[parameters[:format]])
-
elsif use_accept_header && valid_accept_header
-
104
accepts
-
elsif xhr?
-
2
[Mime::JS]
-
else
-
1397
[Mime::HTML]
-
end
-
end
-
-
# Sets the \format by string extension, which can be used to force custom formats
-
# that are not controlled by the extension.
-
#
-
# class ApplicationController < ActionController::Base
-
# before_filter :adjust_format_for_iphone
-
#
-
# private
-
# def adjust_format_for_iphone
-
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
-
# end
-
# end
-
1
def format=(extension)
-
6
parameters[:format] = extension.to_s
-
6
@env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
-
end
-
-
# Sets the \formats by string extensions. This differs from #format= by allowing you
-
# to set multiple, ordered formats, which is useful when you want to have a fallback.
-
#
-
# In this example, the :iphone format will be used if it's available, otherwise it'll fallback
-
# to the :html format.
-
#
-
# class ApplicationController < ActionController::Base
-
# before_filter :adjust_format_for_iphone_with_html_fallback
-
#
-
# private
-
# def adjust_format_for_iphone_with_html_fallback
-
# request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
-
# end
-
# end
-
1
def formats=(extensions)
-
parameters[:format] = extensions.first.to_s
-
@env["action_dispatch.request.formats"] = extensions.collect do |extension|
-
Mime::Type.lookup_by_extension(extension)
-
end
-
end
-
-
# Receives an array of mimes and return the first user sent mime that
-
# matches the order array.
-
#
-
1
def negotiate_mime(order)
-
139
formats.each do |priority|
-
145
if priority == Mime::ALL
-
10
return order.first
-
elsif order.include?(priority)
-
118
return priority
-
end
-
end
-
-
11
order.include?(Mime::ALL) ? formats.first : nil
-
end
-
-
1
protected
-
-
1
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
-
-
1
def valid_accept_header
-
1498
(xhr? && (accept || content_mime_type)) ||
-
2977
(accept && accept !~ BROWSER_LIKE_ACCEPTS)
-
end
-
-
1
def use_accept_header
-
1503
!self.class.ignore_accept_header
-
end
-
end
-
end
-
end
-
1
require 'set'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/string/starts_ends_with'
-
-
1
module Mime
-
1
class Mimes < Array
-
1
def symbols
-
46
@symbols ||= map { |m| m.to_sym }
-
end
-
-
1
%w(<< concat shift unshift push pop []= clear compact! collect!
-
delete delete_at delete_if flatten! map! insert reject! reverse!
-
replace slice! sort! uniq!).each do |method|
-
22
module_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{method}(*)
-
@symbols = nil
-
super
-
end
-
CODE
-
end
-
end
-
-
1
SET = Mimes.new
-
1
EXTENSION_LOOKUP = {}
-
16
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
-
-
1
class << self
-
1
def [](type)
-
2344
return type if type.is_a?(Type)
-
2344
Type.lookup_by_extension(type)
-
end
-
-
1
def fetch(type)
-
300
return type if type.is_a?(Type)
-
570
EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k }
-
end
-
end
-
-
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
-
#
-
# class PostsController < ActionController::Base
-
# def show
-
# @post = Post.find(params[:id])
-
#
-
# respond_to do |format|
-
# format.html
-
# format.ics { render text: post.to_ics, mime_type: Mime::Type["text/calendar"] }
-
# format.xml { render xml: @people }
-
# end
-
# end
-
# end
-
1
class Type
-
1
@@html_types = Set.new [:html, :all]
-
1
cattr_reader :html_types
-
-
# These are the content types which browsers can generate without using ajax, flash, etc
-
# i.e. following a link, getting an image or posting a form. CSRF protection
-
# only needs to protect against these types.
-
1
@@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
-
1
attr_reader :symbol
-
-
1
@register_callbacks = []
-
-
# A simple helper class used in parsing the accept header
-
1
class AcceptItem #:nodoc:
-
1
attr_accessor :index, :name, :q
-
1
alias :to_s :name
-
-
1
def initialize(index, name, q = nil)
-
126
@index = index
-
126
@name = name
-
126
q ||= 0.0 if @name == Mime::ALL.to_s # default wildcard match to end of list
-
126
@q = ((q || 1.0).to_f * 100).to_i
-
end
-
-
1
def <=>(item)
-
184
result = item.q <=> @q
-
184
result = @index <=> item.index if result == 0
-
184
result
-
end
-
-
1
def ==(item)
-
228
@name == item.to_s
-
end
-
end
-
-
1
class AcceptList < Array #:nodoc:
-
1
def assort!
-
24
sort!
-
-
# Take care of the broken text/xml entry by renaming or deleting it
-
24
if text_xml_idx && app_xml_idx
-
13
app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two
-
13
exchange_xml_items if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list
-
13
delete_at(text_xml_idx) # delete text_xml from the list
-
elsif text_xml_idx
-
text_xml.name = Mime::XML.to_s
-
end
-
-
# Look for more specific XML-based types and sort them ahead of app/xml
-
24
if app_xml_idx
-
17
idx = app_xml_idx
-
-
17
while idx < length
-
48
type = self[idx]
-
48
break if type.q < app_xml.q
-
-
35
if type.name.ends_with? '+xml'
-
3
self[app_xml_idx], self[idx] = self[idx], app_xml
-
3
@app_xml_idx = idx
-
end
-
35
idx += 1
-
end
-
end
-
-
137
map! { |i| Mime::Type.lookup(i.name) }.uniq!
-
24
to_a
-
end
-
-
1
private
-
1
def text_xml_idx
-
83
@text_xml_idx ||= index('text/xml')
-
end
-
-
1
def app_xml_idx
-
156
@app_xml_idx ||= index(Mime::XML.to_s)
-
end
-
-
1
def text_xml
-
16
self[text_xml_idx]
-
end
-
-
1
def app_xml
-
80
self[app_xml_idx]
-
end
-
-
1
def exchange_xml_items
-
3
self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml
-
3
@app_xml_idx, @text_xml_idx = text_xml_idx, app_xml_idx
-
end
-
end
-
-
1
class << self
-
1
TRAILING_STAR_REGEXP = /(text|application)\/\*/
-
1
PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
-
-
1
def register_callback(&block)
-
2
@register_callbacks << block
-
end
-
-
1
def lookup(string)
-
1421
LOOKUP[string]
-
end
-
-
1
def lookup_by_extension(extension)
-
2380
EXTENSION_LOOKUP[extension.to_s]
-
end
-
-
# Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for
-
# rendering different HTML versions depending on the user agent, like an iPhone.
-
1
def register_alias(string, symbol, extension_synonyms = [])
-
129
register(string, symbol, [], extension_synonyms, true)
-
end
-
-
1
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
-
234
Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))
-
-
234
new_mime = Mime.const_get(symbol.upcase)
-
234
SET << new_mime
-
-
349
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
-
486
([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
-
-
234
@register_callbacks.each do |callback|
-
420
callback.call(new_mime)
-
end
-
end
-
-
1
def parse(accept_header)
-
142
if accept_header !~ /,/
-
118
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
-
118
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)]
-
else
-
24
list, index = AcceptList.new, 0
-
24
accept_header.split(',').each do |header|
-
112
params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
-
112
if params.present?
-
110
params.strip!
-
-
110
params = parse_trailing_star(params) || [params]
-
-
110
params.each do |m|
-
126
list << AcceptItem.new(index, m.to_s, q)
-
126
index += 1
-
end
-
end
-
end
-
24
list.assort!
-
end
-
end
-
-
1
def parse_trailing_star(accept_header)
-
228
parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP
-
end
-
-
# For an input of <tt>'text'</tt>, returns <tt>[Mime::JSON, Mime::XML, Mime::ICS,
-
# Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]</tt>.
-
#
-
# For an input of <tt>'application'</tt>, returns <tt>[Mime::HTML, Mime::JS,
-
# Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]</tt>.
-
1
def parse_data_with_trailing_star(input)
-
88
Mime::SET.select { |m| m =~ input }
-
end
-
-
# This method is opposite of register method.
-
#
-
# Usage:
-
#
-
# Mime::Type.unregister(:mobile)
-
1
def unregister(symbol)
-
213
symbol = symbol.upcase
-
213
mime = Mime.const_get(symbol)
-
426
Mime.instance_eval { remove_const(symbol) }
-
-
5070
SET.delete_if { |v| v.eql?(mime) }
-
9521
LOOKUP.delete_if { |k,v| v.eql?(mime) }
-
6776
EXTENSION_LOOKUP.delete_if { |k,v| v.eql?(mime) }
-
end
-
end
-
-
1
def initialize(string, symbol = nil, synonyms = [])
-
250
@symbol, @synonyms = symbol, synonyms
-
250
@string = string
-
end
-
-
1
def to_s
-
4025
@string
-
end
-
-
1
def to_str
-
95
to_s
-
end
-
-
1
def to_sym
-
7327
@symbol
-
end
-
-
1
def ref
-
3633
to_sym || to_s
-
end
-
-
1
def ===(list)
-
if list.is_a?(Array)
-
(@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
-
else
-
super
-
end
-
end
-
-
1
def ==(mime_type)
-
423
return false if mime_type.blank?
-
423
(@synonyms + [ self ]).any? do |synonym|
-
899
synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
-
end
-
end
-
-
1
def =~(mime_type)
-
93
return false if mime_type.blank?
-
93
regexp = Regexp.new(Regexp.quote(mime_type.to_s))
-
93
(@synonyms + [ self ]).any? do |synonym|
-
114
synonym.to_s =~ regexp
-
end
-
end
-
-
# Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
-
# ActionController::RequestForgeryProtection.
-
1
def verify_request?
-
23
ActiveSupport::Deprecation.warn "Mime::Type#verify_request? is deprecated and will be removed in Rails 4.1"
-
23
@@browser_generated_types.include?(to_sym)
-
end
-
-
1
def self.browser_generated_types
-
21
ActiveSupport::Deprecation.warn "Mime::Type.browser_generated_types is deprecated and will be removed in Rails 4.1"
-
21
@@browser_generated_types
-
end
-
-
1
def html?
-
322
@@html_types.include?(to_sym) || @string =~ /html/
-
end
-
-
-
1
private
-
-
1
def to_ary; end
-
1
def to_a; end
-
-
1
def method_missing(method, *args)
-
779
if method.to_s.ends_with? '?'
-
779
method[0..-2].downcase.to_sym == to_sym
-
else
-
super
-
end
-
end
-
-
1
def respond_to_missing?(method, include_private = false) #:nodoc:
-
378
method.to_s.ends_with? '?'
-
end
-
end
-
end
-
-
1
require 'action_dispatch/http/mime_types'
-
# Build list of Mime types for HTTP responses
-
# http://www.iana.org/assignments/media-types/
-
-
1
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
-
1
Mime::Type.register "text/plain", :text, [], %w(txt)
-
1
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
-
1
Mime::Type.register "text/css", :css
-
1
Mime::Type.register "text/calendar", :ics
-
1
Mime::Type.register "text/csv", :csv
-
-
1
Mime::Type.register "image/png", :png, [], %w(png)
-
1
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
-
1
Mime::Type.register "image/gif", :gif, [], %w(gif)
-
1
Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
-
1
Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
-
-
1
Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
-
-
1
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
-
1
Mime::Type.register "application/rss+xml", :rss
-
1
Mime::Type.register "application/atom+xml", :atom
-
1
Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )
-
-
1
Mime::Type.register "multipart/form-data", :multipart_form
-
1
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
-
-
# http://www.ietf.org/rfc/rfc4627.txt
-
# http://www.json.org/JSONRequest.html
-
1
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
-
-
1
Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
-
1
Mime::Type.register "application/zip", :zip, [], %w(zip)
-
-
# Create Mime::ALL but do not add it to the SET.
-
1
Mime::ALL = Mime::Type.new("*/*", :all, [])
-
1
module ActionDispatch
-
1
module Http
-
1
class ParameterFilter
-
1
FILTERED = '[FILTERED]'.freeze # :nodoc:
-
-
1
def initialize(filters = [])
-
23
@filters = filters
-
end
-
-
1
def filter(params)
-
1315
compiled_filter.call(params)
-
end
-
-
1
private
-
-
1
def compiled_filter
-
1315
@compiled_filter ||= CompiledFilter.compile(@filters)
-
end
-
-
1
class CompiledFilter # :nodoc:
-
1
def self.compile(filters)
-
1305
return lambda { |params| params.dup } if filters.empty?
-
-
21
strings, regexps, blocks = [], [], []
-
-
21
filters.each do |item|
-
48
case item
-
when Proc
-
7
blocks << item
-
when Regexp
-
4
regexps << item
-
else
-
37
strings << item.to_s
-
end
-
end
-
-
21
regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
-
21
new regexps, blocks
-
end
-
-
1
attr_reader :regexps, :blocks
-
-
1
def initialize(regexps, blocks)
-
21
@regexps = regexps
-
21
@blocks = blocks
-
end
-
-
1
def call(original_params)
-
64
filtered_params = {}
-
-
64
original_params.each do |key, value|
-
347
if regexps.any? { |r| key =~ r }
-
47
value = FILTERED
-
elsif value.is_a?(Hash)
-
30
value = call(value)
-
elsif value.is_a?(Array)
-
16
value = value.map { |v| v.is_a?(Hash) ? call(v) : v }
-
elsif blocks.any?
-
10
key = key.dup
-
10
value = value.dup if value.duplicable?
-
20
blocks.each { |b| b.call(key, value) }
-
end
-
-
145
filtered_params[key] = value
-
end
-
-
64
filtered_params
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActionDispatch
-
1
module Http
-
1
module Parameters
-
1
def initialize(env)
-
10839
super
-
10839
@symbolized_path_params = nil
-
end
-
-
# Returns both GET and POST \parameters in a single hash.
-
1
def parameters
-
4383
@env["action_dispatch.request.parameters"] ||= begin
-
1568
params = request_parameters.merge(query_parameters)
-
1564
params.merge!(path_parameters)
-
1564
encode_params(params).with_indifferent_access
-
end
-
end
-
1
alias :params :parameters
-
-
1
def path_parameters=(parameters) #:nodoc:
-
1642
@symbolized_path_params = nil
-
1642
@env.delete("action_dispatch.request.parameters")
-
1642
@env["action_dispatch.request.path_parameters"] = parameters
-
end
-
-
# The same as <tt>path_parameters</tt> with explicitly symbolized keys.
-
1
def symbolized_path_parameters
-
2024
@symbolized_path_params ||= path_parameters.symbolize_keys
-
end
-
-
# Returns a hash with the \parameters used to form the \path of the request.
-
# Returned hash keys are strings:
-
#
-
# {'action' => 'my_action', 'controller' => 'my_controller'}
-
#
-
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
-
1
def path_parameters
-
9996
@env["action_dispatch.request.path_parameters"] ||= {}
-
end
-
-
1
def reset_parameters #:nodoc:
-
23
@env.delete("action_dispatch.request.parameters")
-
end
-
-
1
private
-
-
# TODO: Validate that the characters are UTF-8. If they aren't,
-
# you'll get a weird error down the road, but our form handling
-
# should really prevent that from happening
-
1
def encode_params(params)
-
4998
if params.is_a?(String)
-
3326
return params.force_encoding("UTF-8").encode!
-
elsif !params.is_a?(Hash)
-
39
return params
-
end
-
-
1633
params.each do |k, v|
-
3415
case v
-
when Hash
-
57
encode_params(v)
-
when Array
-
69
v.map! {|el| encode_params(el) }
-
else
-
3333
encode_params(v)
-
end
-
end
-
end
-
-
# Convert nested Hash to HashWithIndifferentAccess
-
1
def normalize_parameters(value)
-
2099
case value
-
when Hash
-
1976
h = {}
-
2116
value.each { |k, v| h[k] = normalize_parameters(v) }
-
1976
h.with_indifferent_access
-
when Array
-
56
value.map { |e| normalize_parameters(e) }
-
else
-
100
value
-
end
-
end
-
end
-
end
-
end
-
1
require "rack/cache"
-
1
require "rack/cache/context"
-
1
require "active_support/cache"
-
-
1
module ActionDispatch
-
1
class RailsMetaStore < Rack::Cache::MetaStore
-
1
def self.resolve(uri)
-
new
-
end
-
-
1
def initialize(store = Rails.cache)
-
1
@store = store
-
end
-
-
1
def read(key)
-
2
if data = @store.read(key)
-
2
Marshal.load(data)
-
else
-
[]
-
end
-
end
-
-
1
def write(key, value)
-
1
@store.write(key, Marshal.dump(value))
-
end
-
-
1
::Rack::Cache::MetaStore::RAILS = self
-
end
-
-
1
class RailsEntityStore < Rack::Cache::EntityStore
-
1
def self.resolve(uri)
-
new
-
end
-
-
1
def initialize(store = Rails.cache)
-
@store = store
-
end
-
-
1
def exist?(key)
-
@store.exist?(key)
-
end
-
-
1
def open(key)
-
@store.read(key)
-
end
-
-
1
def read(key)
-
body = open(key)
-
body.join if body
-
end
-
-
1
def write(body)
-
buf = []
-
key, size = slurp(body) { |part| buf << part }
-
@store.write(key, buf)
-
[key, size]
-
end
-
-
1
::Rack::Cache::EntityStore::RAILS = self
-
end
-
end
-
1
require 'tempfile'
-
1
require 'stringio'
-
1
require 'strscan'
-
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'active_support/core_ext/string/access'
-
1
require 'active_support/inflector'
-
1
require 'action_dispatch/http/headers'
-
1
require 'action_controller/metal/exceptions'
-
-
1
module ActionDispatch
-
1
class Request < Rack::Request
-
1
include ActionDispatch::Http::Cache::Request
-
1
include ActionDispatch::Http::MimeNegotiation
-
1
include ActionDispatch::Http::Parameters
-
1
include ActionDispatch::Http::FilterParameters
-
1
include ActionDispatch::Http::Upload
-
1
include ActionDispatch::Http::URL
-
-
1
autoload :Session, 'action_dispatch/request/session'
-
-
1
LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
-
-
1
ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
-
PATH_TRANSLATED REMOTE_HOST
-
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
-
SERVER_NAME SERVER_PROTOCOL
-
-
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
-
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
-
HTTP_NEGOTIATE HTTP_PRAGMA ].freeze
-
-
1
ENV_METHODS.each do |env|
-
17
class_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
-
@env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"]
-
end # end
-
METHOD
-
end
-
-
1
def initialize(env)
-
10839
super
-
10839
@method = nil
-
10839
@request_method = nil
-
10839
@remote_ip = nil
-
10839
@original_fullpath = nil
-
10839
@fullpath = nil
-
10839
@ip = nil
-
10839
@uuid = nil
-
end
-
-
1
def key?(key)
-
@env.key?(key)
-
end
-
-
# List of HTTP request methods from the following RFCs:
-
# Hypertext Transfer Protocol -- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt)
-
# HTTP Extensions for Distributed Authoring -- WEBDAV (http://www.ietf.org/rfc/rfc2518.txt)
-
# Versioning Extensions to WebDAV (http://www.ietf.org/rfc/rfc3253.txt)
-
# Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt)
-
# Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt)
-
# Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt)
-
# PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt)
-
1
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
-
1
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
-
1
RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
-
1
RFC3648 = %w(ORDERPATCH)
-
1
RFC3744 = %w(ACL)
-
1
RFC5323 = %w(SEARCH)
-
1
RFC5789 = %w(PATCH)
-
-
1
HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789
-
-
1
HTTP_METHOD_LOOKUP = {}
-
-
# Populate the HTTP method lookup cache
-
1
HTTP_METHODS.each { |method|
-
30
HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym
-
}
-
-
# Returns the HTTP \method that the application should see.
-
# In the case where the \method was overridden by a middleware
-
# (for instance, if a HEAD request was converted to a GET,
-
# or if a _method parameter was used to determine the \method
-
# the application should use), this \method returns the overridden
-
# value, not the original.
-
1
def request_method
-
14532
@request_method ||= check_method(env["REQUEST_METHOD"])
-
end
-
-
# Returns a symbol form of the #request_method
-
1
def request_method_symbol
-
6
HTTP_METHOD_LOOKUP[request_method]
-
end
-
-
# Returns the original value of the environment's REQUEST_METHOD,
-
# even if it was overridden by middleware. See #request_method for
-
# more information.
-
1
def method
-
1541
@method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD'])
-
end
-
-
# Returns a symbol form of the #method
-
1
def method_symbol
-
5
HTTP_METHOD_LOOKUP[method]
-
end
-
-
# Is this a GET (or HEAD) request?
-
# Equivalent to <tt>request.request_method_symbol == :get</tt>.
-
1
def get?
-
1564
HTTP_METHOD_LOOKUP[request_method] == :get
-
end
-
-
# Is this a POST request?
-
# Equivalent to <tt>request.request_method_symbol == :post</tt>.
-
1
def post?
-
11
HTTP_METHOD_LOOKUP[request_method] == :post
-
end
-
-
# Is this a PATCH request?
-
# Equivalent to <tt>request.request_method == :patch</tt>.
-
1
def patch?
-
1
HTTP_METHOD_LOOKUP[request_method] == :patch
-
end
-
-
# Is this a PUT request?
-
# Equivalent to <tt>request.request_method_symbol == :put</tt>.
-
1
def put?
-
1
HTTP_METHOD_LOOKUP[request_method] == :put
-
end
-
-
# Is this a DELETE request?
-
# Equivalent to <tt>request.request_method_symbol == :delete</tt>.
-
1
def delete?
-
52
HTTP_METHOD_LOOKUP[request_method] == :delete
-
end
-
-
# Is this a HEAD request?
-
# Equivalent to <tt>request.request_method_symbol == :head</tt>.
-
1
def head?
-
HTTP_METHOD_LOOKUP[request_method] == :head
-
end
-
-
# Provides access to the request's HTTP headers, for example:
-
#
-
# request.headers["Content-Type"] # => "text/plain"
-
1
def headers
-
28
Http::Headers.new(@env)
-
end
-
-
1
def original_fullpath
-
3
@original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
-
end
-
-
1
def fullpath
-
1573
@fullpath ||= super
-
end
-
-
1
def original_url
-
1
base_url + original_fullpath
-
end
-
-
1
def media_type
-
1211
content_mime_type.to_s
-
end
-
-
# Returns the content length of the request as an integer.
-
1
def content_length
-
301
super.to_i
-
end
-
-
# Returns true if the "X-Requested-With" header contains "XMLHttpRequest"
-
# (case-insensitive). All major JavaScript libraries send this header with
-
# every Ajax request.
-
1
def xml_http_request?
-
2905
@env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i
-
end
-
1
alias :xhr? :xml_http_request?
-
-
1
def ip
-
3602
@ip ||= super
-
end
-
-
# Originating IP address, usually set by the RemoteIp middleware.
-
1
def remote_ip
-
52
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
-
end
-
-
# Returns the unique request id, which is based off either the X-Request-Id header that can
-
# be generated by a firewall, load balancer, or web server or by the RequestId middleware
-
# (which sets the action_dispatch.request_id environment variable).
-
#
-
# This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
-
# This relies on the rack variable set by the ActionDispatch::RequestId middleware.
-
1
def uuid
-
4
@uuid ||= env["action_dispatch.request_id"]
-
end
-
-
# Returns the lowercase name of the HTTP server software.
-
1
def server_software
-
4
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
-
end
-
-
# Read the request \body. This is useful for web services that need to
-
# work with raw requests directly.
-
1
def raw_post
-
42
unless @env.include? 'RAW_POST_DATA'
-
34
@env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
-
34
body.rewind if body.respond_to?(:rewind)
-
end
-
42
@env['RAW_POST_DATA']
-
end
-
-
# The request body is an IO input stream. If the RAW_POST_DATA environment
-
# variable is already set, wrap it in a StringIO.
-
1
def body
-
106
if raw_post = @env['RAW_POST_DATA']
-
70
raw_post.force_encoding(Encoding::BINARY)
-
70
StringIO.new(raw_post)
-
else
-
36
@env['rack.input']
-
end
-
end
-
-
1
def form_data?
-
1541
FORM_DATA_MEDIA_TYPES.include?(content_mime_type.to_s)
-
end
-
-
1
def body_stream #:nodoc:
-
@env['rack.input']
-
end
-
-
# TODO This should be broken apart into AD::Request::Session and probably
-
# be included by the session middleware.
-
1
def reset_session
-
17
if session && session.respond_to?(:destroy)
-
6
session.destroy
-
else
-
11
self.session = {}
-
end
-
17
@env['action_dispatch.request.flash_hash'] = nil
-
end
-
-
1
def session=(session) #:nodoc:
-
4288
Session.set @env, session
-
end
-
-
1
def session_options=(options)
-
4275
Session::Options.set @env, options
-
end
-
-
# Override Rack's GET method to support indifferent access
-
1
def GET
-
2673
@env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {})
-
rescue TypeError => e
-
3
raise ActionController::BadRequest.new(:query, e)
-
end
-
1
alias :query_parameters :GET
-
-
# Override Rack's POST method to support indifferent access
-
1
def POST
-
2983
@env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {})
-
rescue TypeError => e
-
1
raise ActionController::BadRequest.new(:request, e)
-
end
-
1
alias :request_parameters :POST
-
-
# Returns the authorization header regardless of whether it was specified directly or through one of the
-
# proxy alternatives.
-
1
def authorization
-
@env['HTTP_AUTHORIZATION'] ||
-
136
@env['X-HTTP_AUTHORIZATION'] ||
-
@env['X_HTTP_AUTHORIZATION'] ||
-
@env['REDIRECT_X_HTTP_AUTHORIZATION']
-
end
-
-
# True if the request came from localhost, 127.0.0.1.
-
1
def local?
-
7
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
-
end
-
-
1
protected
-
-
# Remove nils from the params hash
-
1
def deep_munge(hash)
-
753
hash.each_value do |v|
-
121
case v
-
when Array
-
36
v.grep(Hash) { |x| deep_munge(x) }
-
23
v.compact!
-
when Hash
-
31
deep_munge(v)
-
end
-
end
-
-
753
hash
-
end
-
-
1
def parse_query(qs)
-
713
deep_munge(super)
-
end
-
-
1
private
-
-
1
def check_method(name)
-
5318
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
-
5315
name
-
end
-
end
-
end
-
1
require 'digest/md5'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'monitor'
-
-
1
module ActionDispatch # :nodoc:
-
# Represents an HTTP response generated by a controller action. Use it to
-
# retrieve the current state of the response, or customize the response. It can
-
# either represent a real HTTP response (i.e. one that is meant to be sent
-
# back to the web browser) or a TestResponse (i.e. one that is generated
-
# from integration tests).
-
#
-
# \Response is mostly a Ruby on \Rails framework implementation detail, and
-
# should never be used directly in controllers. Controllers should use the
-
# methods defined in ActionController::Base instead. For example, if you want
-
# to set the HTTP response's content MIME type, then use
-
# ActionControllerBase#headers instead of Response#headers.
-
#
-
# Nevertheless, integration tests may want to inspect controller responses in
-
# more detail, and that's when \Response can be useful for application
-
# developers. Integration test methods such as
-
# ActionDispatch::Integration::Session#get and
-
# ActionDispatch::Integration::Session#post return objects of type
-
# TestResponse (which are of course also of type \Response).
-
#
-
# For example, the following demo integration test prints the body of the
-
# controller response to the console:
-
#
-
# class DemoControllerTest < ActionDispatch::IntegrationTest
-
# def test_print_root_path_to_console
-
# get('/')
-
# puts response.body
-
# end
-
# end
-
1
class Response
-
1
attr_accessor :request, :header
-
1
attr_reader :status
-
1
attr_writer :sending_file
-
-
1
alias_method :headers=, :header=
-
1
alias_method :headers, :header
-
-
1
delegate :[], :[]=, :to => :@header
-
1
delegate :each, :to => :@stream
-
-
# Sets the HTTP response's content MIME type. For example, in the controller
-
# you could write this:
-
#
-
# response.content_type = "text/plain"
-
#
-
# If a character set has been defined for this response (see charset=) then
-
# the character set information will also be included in the content type
-
# information.
-
1
attr_accessor :charset
-
1
attr_reader :content_type
-
-
1
CONTENT_TYPE = "Content-Type".freeze
-
1
SET_COOKIE = "Set-Cookie".freeze
-
1
LOCATION = "Location".freeze
-
-
2
cattr_accessor(:default_charset) { "utf-8" }
-
1
cattr_accessor(:default_headers)
-
-
1
include Rack::Response::Helpers
-
1
include ActionDispatch::Http::Cache::Response
-
1
include MonitorMixin
-
-
1
class Buffer # :nodoc:
-
1
def initialize(response, buf)
-
6508
@response = response
-
6508
@buf = buf
-
6508
@closed = false
-
end
-
-
1
def write(string)
-
23
raise IOError, "closed stream" if closed?
-
-
22
@response.commit!
-
22
@buf.push string
-
end
-
-
1
def each(&block)
-
1481
@buf.each(&block)
-
end
-
-
1
def close
-
323
@response.commit!
-
323
@closed = true
-
end
-
-
1
def closed?
-
25
@closed
-
end
-
end
-
-
1
attr_reader :stream
-
-
1
def initialize(status = 200, header = {}, body = [])
-
4760
super()
-
-
4760
header = merge_default_headers(header, self.class.default_headers)
-
-
4760
self.body, self.header, self.status = body, header, status
-
-
4760
@sending_file = false
-
4760
@blank = false
-
4760
@cv = new_cond
-
4760
@committed = false
-
4760
@content_type = nil
-
4760
@charset = nil
-
-
4760
if content_type = self[CONTENT_TYPE]
-
763
type, charset = content_type.split(/;\s*charset=/)
-
763
@content_type = Mime::Type.lookup(type)
-
763
@charset = charset || self.class.default_charset
-
end
-
-
4760
prepare_cache_control!
-
-
4760
yield self if block_given?
-
end
-
-
1
def await_commit
-
6
synchronize do
-
17
@cv.wait_until { @committed }
-
end
-
end
-
-
1
def commit!
-
351
synchronize do
-
351
@committed = true
-
351
@cv.broadcast
-
end
-
end
-
-
1
def committed?
-
68
@committed
-
end
-
-
1
def status=(status)
-
8885
@status = Rack::Utils.status_code(status)
-
end
-
-
1
def content_type=(content_type)
-
1357
@content_type = content_type.to_s
-
end
-
-
# The response code of the request
-
1
def response_code
-
836
@status
-
end
-
-
# Returns a String to ensure compatibility with Net::HTTPResponse
-
1
def code
-
16
@status.to_s
-
end
-
-
1
def message
-
61
Rack::Utils::HTTP_STATUS_CODES[@status]
-
end
-
1
alias_method :status_message, :message
-
-
1
def respond_to?(method)
-
933
if method.to_sym == :to_path
-
stream.respond_to?(:to_path)
-
else
-
933
super
-
end
-
end
-
-
1
def to_path
-
stream.to_path
-
end
-
-
1
def body
-
1159
strings = []
-
2318
each { |part| strings << part.to_s }
-
1159
strings.join
-
end
-
-
1
EMPTY = " "
-
-
1
def body=(body)
-
6516
@blank = true if body == EMPTY
-
-
6516
if body.respond_to?(:to_path)
-
8
@stream = body
-
else
-
6508
@stream = build_buffer self, munge_body_object(body)
-
end
-
end
-
-
1
def body_parts
-
14
parts = []
-
30
@stream.each { |x| parts << x }
-
14
parts
-
end
-
-
1
def set_cookie(key, value)
-
2
::Rack::Utils.set_cookie_header!(header, key, value)
-
end
-
-
1
def delete_cookie(key, value={})
-
1
::Rack::Utils.delete_cookie_header!(header, key, value)
-
end
-
-
1
def location
-
251
headers[LOCATION]
-
end
-
1
alias_method :redirect_url, :location
-
-
1
def location=(url)
-
83
headers[LOCATION] = url
-
end
-
-
1
def close
-
312
stream.close if stream.respond_to?(:close)
-
end
-
-
1
def to_a
-
1469
rack_response @status, @header.to_hash
-
end
-
1
alias prepare! to_a
-
1
alias to_ary to_a # For implicit splat on 1.9.2
-
-
# Returns the response cookies, converted to a Hash of (name => value) pairs
-
#
-
# assert_equal 'AuthorOfNewPage', r.cookies['author']
-
1
def cookies
-
18
cookies = {}
-
18
if header = self[SET_COOKIE]
-
15
header = header.split("\n") if header.respond_to?(:to_str)
-
15
header.each do |cookie|
-
19
if pair = cookie.split(';').first
-
55
key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) }
-
19
cookies[key] = value
-
end
-
end
-
end
-
18
cookies
-
end
-
-
1
private
-
-
1
def merge_default_headers(original, default)
-
4760
return original unless default.respond_to?(:merge)
-
-
3
default.merge(original)
-
end
-
-
1
def build_buffer(response, body)
-
6487
Buffer.new response, body
-
end
-
-
1
def munge_body_object(body)
-
6508
body.respond_to?(:each) ? body : [body]
-
end
-
-
1
def assign_default_content_type_and_charset!(headers)
-
1469
return if headers[CONTENT_TYPE].present?
-
-
1461
@content_type ||= Mime::HTML
-
1461
@charset ||= self.class.default_charset
-
-
1461
type = @content_type.to_s.dup
-
1461
type << "; charset=#{@charset}" unless @sending_file
-
-
1461
headers[CONTENT_TYPE] = type
-
end
-
-
1
def rack_response(status, header)
-
1469
assign_default_content_type_and_charset!(header)
-
1469
handle_conditional_get!
-
-
1469
header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
-
-
1469
if [204, 304].include?(@status)
-
16
header.delete CONTENT_TYPE
-
16
[status, header, []]
-
else
-
1453
[status, header, self]
-
end
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
module Http
-
# Models uploaded files.
-
#
-
# The actual file is accessible via the +tempfile+ accessor, though some
-
# of its interface is available directly for convenience.
-
#
-
# Uploaded files are temporary files whose lifespan is one request. When
-
# the object is finalized Ruby unlinks the file, so there is not need to
-
# clean them with a separate maintenance task.
-
1
class UploadedFile
-
# The basename of the file in the client.
-
1
attr_accessor :original_filename
-
-
# A string with the MIME type of the file.
-
1
attr_accessor :content_type
-
-
# A +Tempfile+ object with the actual uploaded file. Note that some of
-
# its interface is available directly.
-
1
attr_accessor :tempfile
-
-
# TODO.
-
1
attr_accessor :headers
-
-
1
def initialize(hash) # :nodoc:
-
24
@tempfile = hash[:tempfile]
-
24
raise(ArgumentError, ':tempfile is required') unless @tempfile
-
-
23
@original_filename = encode_filename(hash[:filename])
-
23
@content_type = hash[:type]
-
23
@headers = hash[:head]
-
end
-
-
# Shortcut for +tempfile.read+.
-
1
def read(length=nil, buffer=nil)
-
9
@tempfile.read(length, buffer)
-
end
-
-
# Shortcut for +tempfile.open+.
-
1
def open
-
1
@tempfile.open
-
end
-
-
# Shortcut for +tempfile.close+.
-
1
def close(unlink_now=false)
-
2
@tempfile.close(unlink_now)
-
end
-
-
# Shortcut for +tempfile.path+.
-
1
def path
-
1
@tempfile.path
-
end
-
-
# Shortcut for +tempfile.rewind+.
-
1
def rewind
-
@tempfile.rewind
-
end
-
-
# Shortcut for +tempfile.size+.
-
1
def size
-
2
@tempfile.size
-
end
-
-
# Shortcut for +tempfile.eof?+.
-
1
def eof?
-
1
@tempfile.eof?
-
end
-
-
1
private
-
-
1
def encode_filename(filename)
-
# Encode the filename in the utf8 encoding, unless it is nil
-
23
filename.force_encoding("UTF-8").encode! if filename
-
end
-
end
-
-
1
module Upload # :nodoc:
-
# Convert nested Hash to HashWithIndifferentAccess and replace
-
# file upload hash with UploadedFile objects
-
1
def normalize_parameters(value)
-
2107
if Hash === value && value.has_key?(:tempfile)
-
8
UploadedFile.new(value)
-
else
-
2099
super
-
end
-
end
-
1
private :normalize_parameters
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
module Http
-
1
module URL
-
1
IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
-
-
1
mattr_accessor :tld_length
-
1
self.tld_length = 1
-
-
1
class << self
-
1
def extract_domain(host, tld_length = @@tld_length)
-
18
return nil unless named_host?(host)
-
16
host.split('.').last(1 + tld_length).join('.')
-
end
-
-
1
def extract_subdomains(host, tld_length = @@tld_length)
-
41
return [] unless named_host?(host)
-
37
parts = host.split('.')
-
37
parts[0..-(tld_length+2)]
-
end
-
-
1
def extract_subdomain(host, tld_length = @@tld_length)
-
32
extract_subdomains(host, tld_length).join('.')
-
end
-
-
1
def url_for(options = {})
-
2376
path = ""
-
2376
path << options.delete(:script_name).to_s.chomp("/")
-
2376
path << options.delete(:path).to_s
-
-
2376
params = options[:params] || {}
-
3616
params.reject! {|k,v| v.to_param.nil? }
-
-
2376
result = build_host_url(options)
-
-
2387
result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
-
2374
result << "?#{params.to_query}" unless params.empty?
-
2374
result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
-
2374
result
-
end
-
-
1
private
-
-
1
def build_host_url(options)
-
2376
if options[:host].blank? && options[:only_path].blank?
-
2
raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
-
end
-
-
2374
result = ""
-
-
2374
unless options[:only_path]
-
225
unless options[:protocol] == false
-
224
result << (options[:protocol] || "http")
-
224
result << ":" unless result.match(%r{:|//})
-
end
-
225
result << "//" unless result.match("//")
-
225
result << rewrite_authentication(options)
-
225
result << host_or_subdomain_and_domain(options)
-
225
result << ":#{options.delete(:port)}" if options[:port]
-
end
-
2374
result
-
end
-
-
1
def named_host?(host)
-
284
host && IP_HOST_REGEXP !~ host
-
end
-
-
1
def rewrite_authentication(options)
-
225
if options[:user] && options[:password]
-
3
"#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
-
else
-
222
""
-
end
-
end
-
-
1
def host_or_subdomain_and_domain(options)
-
225
return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
-
-
15
tld_length = options[:tld_length] || @@tld_length
-
-
15
host = ""
-
15
unless options[:subdomain] == false
-
12
host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param
-
12
host << "."
-
end
-
15
host << (options[:domain] || extract_domain(options[:host], tld_length))
-
15
host
-
end
-
end
-
-
1
def initialize(env)
-
10839
super
-
10839
@protocol = nil
-
10839
@port = nil
-
end
-
-
# Returns the complete URL used for this request.
-
1
def url
-
22
protocol + host_with_port + fullpath
-
end
-
-
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
-
1
def protocol
-
3419
@protocol ||= ssl? ? 'https://' : 'http://'
-
end
-
-
# Returns the \host for this request, such as "example.com".
-
1
def raw_host_with_port
-
3364
if forwarded = env["HTTP_X_FORWARDED_HOST"]
-
8
forwarded.split(/,\s?/).last
-
else
-
3356
env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
-
end
-
end
-
-
# Returns the host for this request, such as example.com.
-
1
def host
-
2257
raw_host_with_port.sub(/:\d+$/, '')
-
end
-
-
# Returns a \host:\port string for this request, such as "example.com" or
-
# "example.com:8080".
-
1
def host_with_port
-
90
"#{host}#{port_string}"
-
end
-
-
# Returns the port number of this request as an integer.
-
1
def port
-
@port ||= begin
-
1107
if raw_host_with_port =~ /:(\d+)$/
-
26
$1.to_i
-
else
-
1081
standard_port
-
end
-
1188
end
-
end
-
-
# Returns the standard \port number for this request's protocol.
-
1
def standard_port
-
2259
case protocol
-
11
when 'https://' then 443
-
2248
else 80
-
end
-
end
-
-
# Returns whether this request is using the standard port
-
1
def standard_port?
-
1176
port == standard_port
-
end
-
-
# Returns a number \port suffix like 8080 if the \port number of this request
-
# is not the default HTTP \port 80 or HTTPS \port 443.
-
1
def optional_port
-
1060
standard_port? ? nil : port
-
end
-
-
# Returns a string \port suffix, including colon, like ":8080" if the \port
-
# number of this request is not the default HTTP \port 80 or HTTPS \port 443.
-
1
def port_string
-
92
standard_port? ? '' : ":#{port}"
-
end
-
-
1
def server_port
-
1
@env['SERVER_PORT'].to_i
-
end
-
-
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
-
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
-
1
def domain(tld_length = @@tld_length)
-
6
ActionDispatch::Http::URL.extract_domain(host, tld_length)
-
end
-
-
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
-
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
-
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
-
# in "www.rubyonrails.co.uk".
-
1
def subdomains(tld_length = @@tld_length)
-
9
ActionDispatch::Http::URL.extract_subdomains(host, tld_length)
-
end
-
-
# Returns all the \subdomains as a string, so <tt>"dev.www"</tt> would be
-
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
-
# such as 2 to catch <tt>"www"</tt> instead of <tt>"www.rubyonrails"</tt>
-
# in "www.rubyonrails.co.uk".
-
1
def subdomain(tld_length = @@tld_length)
-
29
ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
-
end
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
class BestStandardsSupport
-
1
def initialize(app, type = true)
-
4
@app = app
-
-
4
@header = case type
-
when true
-
2
"IE=Edge,chrome=1"
-
when :builtin
-
1
"IE=Edge"
-
when false
-
1
nil
-
end
-
end
-
-
1
def call(env)
-
4
status, headers, body = @app.call(env)
-
-
4
if headers["X-UA-Compatible"] && @header
-
1
headers["X-UA-Compatible"] << "," << @header.to_s
-
else
-
3
headers["X-UA-Compatible"] = @header
-
end
-
-
4
[status, headers, body]
-
end
-
end
-
end
-
-
1
module ActionDispatch
-
# Provide callbacks to be executed before and after the request dispatch.
-
1
class Callbacks
-
1
include ActiveSupport::Callbacks
-
-
1
define_callbacks :call
-
-
1
class << self
-
1
delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
-
end
-
-
1
def self.before(*args, &block)
-
1
set_callback(:call, :before, *args, &block)
-
end
-
-
1
def self.after(*args, &block)
-
1
set_callback(:call, :after, *args, &block)
-
end
-
-
1
def initialize(app)
-
198
@app = app
-
end
-
-
1
def call(env)
-
296
error = nil
-
296
result = run_callbacks :call do
-
296
begin
-
296
@app.call(env)
-
rescue => error
-
end
-
end
-
296
raise error if error
-
275
result
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
-
1
module ActionDispatch
-
1
class Request < Rack::Request
-
1
def cookie_jar
-
7320
env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
-
end
-
end
-
-
# \Cookies are read and written through ActionController#cookies.
-
#
-
# The cookies being read are the ones received along with the request, the cookies
-
# being written will be sent out with the response. Reading a cookie does not get
-
# the cookie object itself back, just the value it holds.
-
#
-
# Examples for writing:
-
#
-
# # Sets a simple session cookie.
-
# # This cookie will be deleted when the user's browser is closed.
-
# cookies[:user_name] = "david"
-
#
-
# # Assign an array of values to a cookie.
-
# cookies[:lat_lon] = [47.68, -122.37]
-
#
-
# # Sets a cookie that expires in 1 hour.
-
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
-
#
-
# # Sets a signed cookie, which prevents users from tampering with its value.
-
# # The cookie is signed by your app's <tt>config.secret_token</tt> value.
-
# # It can be read using the signed method <tt>cookies.signed[:key]</tt>
-
# cookies.signed[:user_id] = current_user.id
-
#
-
# # Sets a "permanent" cookie (which expires in 20 years from now).
-
# cookies.permanent[:login] = "XJ-122"
-
#
-
# # You can also chain these methods:
-
# cookies.permanent.signed[:login] = "XJ-122"
-
#
-
# Examples for reading:
-
#
-
# cookies[:user_name] # => "david"
-
# cookies.size # => 2
-
# cookies[:lat_lon] # => [47.68, -122.37]
-
# cookies.signed[:login] # => "XJ-122"
-
#
-
# Example for deleting:
-
#
-
# cookies.delete :user_name
-
#
-
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
-
#
-
# cookies[:key] = {
-
# value: 'a yummy cookie',
-
# expires: 1.year.from_now,
-
# domain: 'domain.com'
-
# }
-
#
-
# cookies.delete(:key, domain: 'domain.com')
-
#
-
# The option symbols for setting cookies are:
-
#
-
# * <tt>:value</tt> - The cookie's value or list of values (as an array).
-
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
-
# of the application.
-
# * <tt>:domain</tt> - The domain for which this cookie applies so you can
-
# restrict to the domain level. If you use a schema like www.example.com
-
# and want to share session with user.example.com set <tt>:domain</tt>
-
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
-
# <tt>:all</tt> again when deleting keys.
-
#
-
# domain: nil # Does not sets cookie domain. (default)
-
# domain: :all # Allow the cookie for the top most level
-
# domain and subdomains.
-
#
-
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
-
# * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
-
# Default is +false+.
-
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
-
# only HTTP. Defaults to +false+.
-
1
class Cookies
-
1
HTTP_HEADER = "Set-Cookie".freeze
-
1
TOKEN_KEY = "action_dispatch.secret_token".freeze
-
-
# Raised when storing more than 4K of session data.
-
1
CookieOverflow = Class.new StandardError
-
-
1
class CookieJar #:nodoc:
-
1
include Enumerable
-
-
# This regular expression is used to split the levels of a domain.
-
# The top level domain can be any string without a period or
-
# **.**, ***.** style TLDs like co.uk or com.au
-
#
-
# www.example.co.uk gives:
-
# $& => example.co.uk
-
#
-
# example.com gives:
-
# $& => example.com
-
#
-
# lots.of.subdomains.example.local gives:
-
# $& => example.local
-
1
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
-
-
1
def self.build(request)
-
999
secret = request.env[TOKEN_KEY]
-
999
host = request.host
-
999
secure = request.ssl?
-
-
999
new(secret, host, secure).tap do |hash|
-
999
hash.update(request.cookies)
-
end
-
end
-
-
1
def initialize(secret = nil, host = nil, secure = false)
-
1001
@secret = secret
-
1001
@set_cookies = {}
-
1001
@delete_cookies = {}
-
1001
@host = host
-
1001
@secure = secure
-
1001
@cookies = {}
-
end
-
-
1
def each(&block)
-
2
@cookies.each(&block)
-
end
-
-
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
-
1
def [](name)
-
83
@cookies[name.to_s]
-
end
-
-
1
def key?(name)
-
4
@cookies.key?(name.to_s)
-
end
-
1
alias :has_key? :key?
-
-
1
def update(other_hash)
-
4546
@cookies.update other_hash.stringify_keys
-
4546
self
-
end
-
-
1
def handle_options(options) #:nodoc:
-
123
options[:path] ||= "/"
-
-
123
if options[:domain] == :all
-
# if there is a provided tld length then we use it otherwise default domain regexp
-
14
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
-
-
# if host is not ip and matches domain regexp
-
# (ip confirms to domain regexp so we explicitly check for ip)
-
14
options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
-
11
".#{$&}"
-
end
-
109
elsif options[:domain].is_a? Array
-
# if host matches one of the supplied domains without a dot in front of it
-
17
options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
-
end
-
end
-
-
# Sets the cookie named +name+. The second argument may be the very cookie
-
# value, or a hash of options as documented above.
-
1
def []=(key, options)
-
106
if options.is_a?(Hash)
-
74
options.symbolize_keys!
-
74
value = options[:value]
-
else
-
32
value = options
-
32
options = { :value => value }
-
end
-
-
106
handle_options(options)
-
-
106
if @cookies[key.to_s] != value or options[:expires]
-
93
@cookies[key.to_s] = value
-
93
@set_cookies[key.to_s] = options
-
93
@delete_cookies.delete(key.to_s)
-
end
-
-
106
value
-
end
-
-
# Removes the cookie on the client machine by setting the value to an empty string
-
# and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
-
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
-
1
def delete(key, options = {})
-
15
return unless @cookies.has_key? key.to_s
-
-
14
options.symbolize_keys!
-
14
handle_options(options)
-
-
14
value = @cookies.delete(key.to_s)
-
14
@delete_cookies[key.to_s] = options
-
14
value
-
end
-
-
# Whether the given cookie is to be deleted by this CookieJar.
-
# Like <tt>[]=</tt>, you can pass in an options hash to test if a
-
# deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
-
1
def deleted?(key, options = {})
-
3
options.symbolize_keys!
-
3
handle_options(options)
-
3
@delete_cookies[key.to_s] == options
-
end
-
-
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
-
1
def clear(options = {})
-
7
@cookies.each_key{ |k| delete(k, options) }
-
end
-
-
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
-
#
-
# cookies.permanent[:prefers_open_id] = true
-
# # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
-
#
-
# This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
-
#
-
# This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
-
#
-
# cookies.permanent.signed[:remember_me] = current_user.id
-
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
-
1
def permanent
-
3
@permanent ||= PermanentCookieJar.new(self, @secret)
-
end
-
-
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
-
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
-
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
-
# be raised.
-
#
-
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+.
-
#
-
# Example:
-
#
-
# cookies.signed[:discount] = 45
-
# # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
-
#
-
# cookies.signed[:discount] # => 45
-
1
def signed
-
87
@signed ||= SignedCookieJar.new(self, @secret)
-
end
-
-
1
def write(headers)
-
1262
@set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
-
1189
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
-
end
-
-
1
def recycle! #:nodoc:
-
1182
@set_cookies.clear
-
1182
@delete_cookies.clear
-
end
-
-
1
mattr_accessor :always_write_cookie
-
1
self.always_write_cookie = false
-
-
1
private
-
-
1
def write_cookie?(cookie)
-
80
@secure || !cookie[:secure] || always_write_cookie
-
end
-
end
-
-
1
class PermanentCookieJar < CookieJar #:nodoc:
-
1
def initialize(parent_jar, secret)
-
3
@parent_jar, @secret = parent_jar, secret
-
end
-
-
1
def []=(key, options)
-
3
if options.is_a?(Hash)
-
1
options.symbolize_keys!
-
else
-
2
options = { :value => options }
-
end
-
-
3
options[:expires] = 20.years.from_now
-
3
@parent_jar[key] = options
-
end
-
-
1
def method_missing(method, *arguments, &block)
-
@parent_jar.send(method, *arguments, &block)
-
end
-
end
-
-
1
class SignedCookieJar < CookieJar #:nodoc:
-
1
MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
-
1
SECRET_MIN_LENGTH = 30 # Characters
-
-
1
def initialize(parent_jar, secret)
-
52
ensure_secret_secure(secret)
-
47
@parent_jar = parent_jar
-
47
@verifier = ActiveSupport::MessageVerifier.new(secret)
-
end
-
-
1
def [](name)
-
45
if signed_message = @parent_jar[name]
-
25
@verifier.verify(signed_message)
-
end
-
rescue ActiveSupport::MessageVerifier::InvalidSignature
-
2
nil
-
end
-
-
1
def []=(key, options)
-
37
if options.is_a?(Hash)
-
33
options.symbolize_keys!
-
33
options[:value] = @verifier.generate(options[:value])
-
else
-
4
options = { :value => @verifier.generate(options) }
-
end
-
-
37
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
-
35
@parent_jar[key] = options
-
end
-
-
1
def method_missing(method, *arguments, &block)
-
@parent_jar.send(method, *arguments, &block)
-
end
-
-
1
protected
-
-
# To prevent users from using something insecure like "Password" we make sure that the
-
# secret they've provided is at least 30 characters in length.
-
1
def ensure_secret_secure(secret)
-
52
if secret.blank?
-
2
raise ArgumentError, "A secret is required to generate an " +
-
"integrity hash for cookie session data. Use " +
-
"config.secret_token = \"some secret phrase of at " +
-
"least #{SECRET_MIN_LENGTH} characters\"" +
-
"in config/initializers/secret_token.rb"
-
end
-
-
50
if secret.length < SECRET_MIN_LENGTH
-
3
raise ArgumentError, "Secret should be something secure, " +
-
"like \"#{SecureRandom.hex(16)}\". The value you " +
-
"provided, \"#{secret}\", is shorter than the minimum length " +
-
"of #{SECRET_MIN_LENGTH} characters"
-
end
-
end
-
end
-
-
1
def initialize(app)
-
195
@app = app
-
end
-
-
1
def call(env)
-
287
status, headers, body = @app.call(env)
-
-
273
if cookie_jar = env['action_dispatch.cookies']
-
59
cookie_jar.write(headers)
-
59
if headers[HTTP_HEADER].respond_to?(:join)
-
headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
-
end
-
end
-
-
273
[status, headers, body]
-
end
-
end
-
end
-
1
require 'action_dispatch/http/request'
-
1
require 'action_dispatch/middleware/exception_wrapper'
-
1
require 'action_dispatch/routing/inspector'
-
-
-
1
module ActionDispatch
-
# This middleware is responsible for logging exceptions and
-
# showing a debugging page in case the request is local.
-
1
class DebugExceptions
-
1
RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
-
-
1
def initialize(app, routes_app = nil)
-
207
@app = app
-
207
@routes_app = routes_app
-
end
-
-
1
def call(env)
-
321
begin
-
321
response = @app.call(env)
-
-
275
if response[1]['X-Cascade'] == 'pass'
-
3
body = response[2]
-
3
body.close if body.respond_to?(:close)
-
3
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
-
end
-
rescue Exception => exception
-
49
raise exception if env['action_dispatch.show_exceptions'] == false
-
end
-
-
312
exception ? render_exception(env, exception) : response
-
end
-
-
1
private
-
-
1
def render_exception(env, exception)
-
40
wrapper = ExceptionWrapper.new(env, exception)
-
40
log_error(env, wrapper)
-
-
40
if env['action_dispatch.show_detailed_exceptions']
-
19
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
-
:request => Request.new(env),
-
:exception => wrapper.exception,
-
:application_trace => wrapper.application_trace,
-
:framework_trace => wrapper.framework_trace,
-
:full_trace => wrapper.full_trace,
-
:routes => formatted_routes(exception)
-
)
-
-
19
file = "rescues/#{wrapper.rescue_template}"
-
19
body = template.render(:template => file, :layout => 'rescues/layout')
-
19
render(wrapper.status_code, body)
-
else
-
21
raise exception
-
end
-
end
-
-
1
def render(status, body)
-
19
[status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
-
end
-
-
1
def log_error(env, wrapper)
-
40
logger = logger(env)
-
40
return unless logger
-
-
5
exception = wrapper.exception
-
-
5
trace = wrapper.application_trace
-
5
trace = wrapper.framework_trace if trace.empty?
-
-
5
ActiveSupport::Deprecation.silence do
-
5
message = "\n#{exception.class} (#{exception.message}):\n"
-
5
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
-
5
message << " " << trace.join("\n ")
-
5
logger.fatal("#{message}\n\n")
-
end
-
end
-
-
1
def logger(env)
-
40
env['action_dispatch.logger'] || stderr_logger
-
end
-
-
1
def stderr_logger
-
@stderr_logger ||= ActiveSupport::Logger.new($stderr)
-
end
-
-
1
def formatted_routes(exception)
-
19
return false unless @routes_app.respond_to?(:routes)
-
if exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)
-
inspector = ActionDispatch::Routing::RoutesInspector.new
-
inspector.format(@routes_app.routes.routes).join("\n")
-
end
-
end
-
end
-
end
-
1
require 'action_controller/metal/exceptions'
-
1
require 'active_support/core_ext/exception'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
-
1
module ActionDispatch
-
1
class ExceptionWrapper
-
1
cattr_accessor :rescue_responses
-
1
@@rescue_responses = Hash.new(:internal_server_error)
-
1
@@rescue_responses.merge!(
-
'ActionController::RoutingError' => :not_found,
-
'AbstractController::ActionNotFound' => :not_found,
-
'ActionController::MethodNotAllowed' => :method_not_allowed,
-
'ActionController::NotImplemented' => :not_implemented,
-
'ActionController::UnknownFormat' => :not_acceptable,
-
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
-
'ActionController::BadRequest' => :bad_request
-
)
-
-
1
cattr_accessor :rescue_templates
-
1
@@rescue_templates = Hash.new('diagnostics')
-
1
@@rescue_templates.merge!(
-
'ActionView::MissingTemplate' => 'missing_template',
-
'ActionController::RoutingError' => 'routing_error',
-
'AbstractController::ActionNotFound' => 'unknown_action',
-
'ActionView::Template::Error' => 'template_error'
-
)
-
-
1
attr_reader :env, :exception
-
-
1
def initialize(env, exception)
-
63
@env = env
-
63
@exception = original_exception(exception)
-
end
-
-
1
def rescue_template
-
19
@@rescue_templates[@exception.class.name]
-
end
-
-
1
def status_code
-
42
self.class.status_code_for_exception(@exception.class.name)
-
end
-
-
1
def application_trace
-
24
clean_backtrace(:silent)
-
end
-
-
1
def framework_trace
-
20
clean_backtrace(:noise)
-
end
-
-
1
def full_trace
-
19
clean_backtrace(:all)
-
end
-
-
1
def self.status_code_for_exception(class_name)
-
64
Rack::Utils.status_code(@@rescue_responses[class_name])
-
end
-
-
1
private
-
-
1
def original_exception(exception)
-
63
if registered_original_exception?(exception)
-
3
exception.original_exception
-
else
-
60
exception
-
end
-
end
-
-
1
def registered_original_exception?(exception)
-
63
exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
-
end
-
-
1
def clean_backtrace(*args)
-
63
if backtrace_cleaner
-
5
backtrace_cleaner.clean(@exception.backtrace, *args)
-
else
-
58
@exception.backtrace
-
end
-
end
-
-
1
def backtrace_cleaner
-
68
@backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
class Request < Rack::Request
-
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
-
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
-
# to put a new one.
-
1
def flash
-
1273
@env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new).tap(&:sweep)
-
end
-
end
-
-
# The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
-
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
-
# action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
-
# then expose the flash to its template. Actually, that exposure is automatically done.
-
#
-
# class PostsController < ActionController::Base
-
# def create
-
# # save post
-
# flash[:notice] = "Post successfully created"
-
# redirect_to @post
-
# end
-
#
-
# def show
-
# # doesn't need to assign the flash notice to the template, that's done automatically
-
# end
-
# end
-
#
-
# show.html.erb
-
# <% if flash[:notice] %>
-
# <div class="notice"><%= flash[:notice] %></div>
-
# <% end %>
-
#
-
# Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available:
-
#
-
# flash.alert = "You must be logged in"
-
# flash.notice = "Post successfully created"
-
#
-
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as
-
# many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
-
#
-
# See docs on the FlashHash class for more details about the flash.
-
1
class Flash
-
1
KEY = 'action_dispatch.request.flash_hash'.freeze
-
-
1
class FlashNow #:nodoc:
-
1
attr_accessor :flash
-
-
1
def initialize(flash)
-
5
@flash = flash
-
end
-
-
1
def []=(k, v)
-
7
@flash[k] = v
-
7
@flash.discard(k)
-
7
v
-
end
-
-
1
def [](k)
-
3
@flash[k]
-
end
-
-
# Convenience accessor for flash.now[:alert]=
-
1
def alert=(message)
-
1
self[:alert] = message
-
end
-
-
# Convenience accessor for flash.now[:notice]=
-
1
def notice=(message)
-
1
self[:notice] = message
-
end
-
end
-
-
# Implementation detail: please do not change the signature of the
-
# FlashHash class. Doing that will likely affect all Rails apps in
-
# production as the FlashHash currently stored in their sessions will
-
# become invalid.
-
1
class FlashHash
-
1
include Enumerable
-
-
1
def initialize #:nodoc:
-
1184
@discard = Set.new
-
1184
@flashes = {}
-
1184
@now = nil
-
end
-
-
1
def initialize_copy(other)
-
14
if other.now_is_loaded?
-
4
@now = other.now.dup
-
4
@now.flash = self
-
end
-
14
super
-
end
-
-
1
def []=(k, v) #:nodoc:
-
52
@discard.delete k
-
52
@flashes[k] = v
-
end
-
-
1
def [](k)
-
51
@flashes[k]
-
end
-
-
1
def update(h) #:nodoc:
-
1184
@discard.subtract h.keys
-
1184
@flashes.update h
-
1184
self
-
end
-
-
1
def keys
-
21
@flashes.keys
-
end
-
-
1
def key?(name)
-
2
@flashes.key? name
-
end
-
-
1
def delete(key)
-
3
@discard.delete key
-
3
@flashes.delete key
-
3
self
-
end
-
-
1
def to_hash
-
35
@flashes.dup
-
end
-
-
1
def empty?
-
1147
@flashes.empty?
-
end
-
-
1
def clear
-
3
@discard.clear
-
3
@flashes.clear
-
end
-
-
1
def each(&block)
-
2
@flashes.each(&block)
-
end
-
-
1
alias :merge! :update
-
-
1
def replace(h) #:nodoc:
-
2
@discard.clear
-
2
@flashes.replace h
-
2
self
-
end
-
-
# Sets a flash that will not be available to the next action, only to the current.
-
#
-
# flash.now[:message] = "Hello current action"
-
#
-
# This method enables you to use the flash as a central messaging system in your app.
-
# When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
-
# When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
-
# vanish when the current action is done.
-
#
-
# Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
-
1
def now
-
13
@now ||= FlashNow.new(self)
-
end
-
-
# Keeps either the entire current flash or a specific flash entry available for the next action:
-
#
-
# flash.keep # keeps the entire flash
-
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
-
1
def keep(k = nil)
-
17
@discard.subtract Array(k || keys)
-
17
k ? self[k] : self
-
end
-
-
# Marks the entire flash or a single flash entry to be discarded by the end of the current action:
-
#
-
# flash.discard # discard the entire flash at the end of the current action
-
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
-
1
def discard(k = nil)
-
15
@discard.merge Array(k || keys)
-
15
k ? self[k] : self
-
end
-
-
# Mark for removal entries that were kept, and delete unkept ones.
-
#
-
# This method is called automatically by filters, so you generally don't need to care about it.
-
1
def sweep #:nodoc:
-
1207
@discard.each { |k| @flashes.delete k }
-
1198
@discard.replace @flashes.keys
-
end
-
-
# Convenience accessor for flash[:alert]
-
1
def alert
-
self[:alert]
-
end
-
-
# Convenience accessor for flash[:alert]=
-
1
def alert=(message)
-
self[:alert] = message
-
end
-
-
# Convenience accessor for flash[:notice]
-
1
def notice
-
self[:notice]
-
end
-
-
# Convenience accessor for flash[:notice]=
-
1
def notice=(message)
-
self[:notice] = message
-
end
-
-
1
protected
-
1
def now_is_loaded?
-
14
@now
-
end
-
end
-
-
1
def initialize(app)
-
200
@app = app
-
end
-
-
1
def call(env)
-
295
@app.call(env)
-
ensure
-
295
session = Request::Session.find(env) || {}
-
295
flash_hash = env[KEY]
-
-
295
if flash_hash
-
16
if !flash_hash.empty? || session.key?('flash')
-
14
session["flash"] = flash_hash
-
14
new_hash = flash_hash.dup
-
else
-
2
new_hash = flash_hash
-
end
-
-
16
env[KEY] = new_hash
-
end
-
-
if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
-
295
session.key?('flash') && session['flash'].empty?
-
session.delete('flash')
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/conversions'
-
1
require 'action_dispatch/http/request'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActionDispatch
-
1
class ParamsParser
-
1
class ParseError < StandardError
-
1
attr_reader :original_exception
-
-
1
def initialize(message, original_exception)
-
6
super(message)
-
6
@original_exception = original_exception
-
end
-
end
-
-
1
DEFAULT_PARSERS = {
-
Mime::XML => :xml_simple,
-
Mime::JSON => :json
-
}
-
-
1
def initialize(app, parsers = {})
-
202
@app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
-
end
-
-
1
def call(env)
-
300
if params = parse_formatted_parameters(env)
-
27
env["action_dispatch.request.request_parameters"] = params
-
end
-
-
294
@app.call(env)
-
end
-
-
1
private
-
1
def parse_formatted_parameters(env)
-
300
request = Request.new(env)
-
-
300
return false if request.content_length.zero?
-
-
54
mime_type = content_type_from_legacy_post_data_format_header(env) ||
-
request.content_mime_type
-
-
54
strategy = @parsers[mime_type]
-
-
54
return false unless strategy
-
-
33
case strategy
-
when Proc
-
3
strategy.call(request.raw_post)
-
when :xml_simple, :xml_node
-
22
data = Hash.from_xml(request.raw_post) || {}
-
18
data.with_indifferent_access
-
when :yaml
-
2
YAML.load(request.raw_post)
-
when :json
-
6
data = ActiveSupport::JSON.decode(request.raw_post)
-
4
data = {:_json => data} unless data.is_a?(Hash)
-
4
data.with_indifferent_access
-
else
-
false
-
end
-
rescue Exception => e # YAML, XML or Ruby code block errors
-
6
logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
-
-
6
raise ParseError.new(e.message, e)
-
end
-
-
1
def content_type_from_legacy_post_data_format_header(env)
-
54
if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
-
5
case x_post_format.to_s.downcase
-
when 'yaml' then return Mime::YAML
-
5
when 'xml' then return Mime::XML
-
end
-
end
-
-
nil
-
end
-
-
1
def logger(env)
-
6
env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
class PublicExceptions
-
1
attr_accessor :public_path
-
-
1
def initialize(public_path)
-
197
@public_path = public_path
-
end
-
-
1
def call(env)
-
20
exception = env["action_dispatch.exception"]
-
20
status = env["PATH_INFO"][1..-1]
-
20
request = ActionDispatch::Request.new(env)
-
20
content_type = request.formats.first
-
20
body = { :status => status, :error => exception.message }
-
-
20
render(status, content_type, body)
-
end
-
-
1
private
-
-
1
def render(status, content_type, body)
-
20
format = content_type && "to_#{content_type.to_sym}"
-
20
if format && body.respond_to?(format)
-
2
render_format(status, content_type, body.public_send(format))
-
else
-
18
render_html(status)
-
end
-
end
-
-
1
def render_format(status, content_type, body)
-
13
[status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
-
'Content-Length' => body.bytesize.to_s}, [body]]
-
end
-
-
1
def render_html(status)
-
18
found = false
-
18
path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
-
18
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
-
-
18
if found || File.exist?(path)
-
11
render_format(status, 'text/html', File.read(path))
-
else
-
7
[404, { "X-Cascade" => "pass" }, []]
-
end
-
end
-
end
-
end
-
1
module ActionDispatch
-
# ActionDispatch::Reloader provides prepare and cleanup callbacks,
-
# intended to assist with code reloading during development.
-
#
-
# Prepare callbacks are run before each request, and cleanup callbacks
-
# after each request. In this respect they are analogs of ActionDispatch::Callback's
-
# before and after callbacks. However, cleanup callbacks are not called until the
-
# request is fully complete -- that is, after #close has been called on
-
# the response body. This is important for streaming responses such as the
-
# following:
-
#
-
# self.response_body = lambda { |response, output|
-
# # code here which refers to application models
-
# }
-
#
-
# Cleanup callbacks will not be called until after the response_body lambda
-
# is evaluated, ensuring that it can refer to application models and other
-
# classes before they are unloaded.
-
#
-
# By default, ActionDispatch::Reloader is included in the middleware stack
-
# only in the development environment; specifically, when +config.cache_classes+
-
# is false. Callbacks may be registered even when it is not included in the
-
# middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
-
# or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
-
#
-
1
class Reloader
-
1
include ActiveSupport::Callbacks
-
-
1
define_callbacks :prepare, :scope => :name
-
1
define_callbacks :cleanup, :scope => :name
-
-
# Add a prepare callback. Prepare callbacks are run before each request, prior
-
# to ActionDispatch::Callback's before callbacks.
-
1
def self.to_prepare(*args, &block)
-
10
set_callback(:prepare, *args, &block)
-
end
-
-
# Add a cleanup callback. Cleanup callbacks are run after each request is
-
# complete (after #close is called on the response body).
-
1
def self.to_cleanup(*args, &block)
-
4
set_callback(:cleanup, *args, &block)
-
end
-
-
# Execute all prepare callbacks.
-
1
def self.prepare!
-
3
new(nil).prepare!
-
end
-
-
# Execute all cleanup callbacks.
-
1
def self.cleanup!
-
2
new(nil).cleanup!
-
end
-
-
1
def initialize(app, condition=nil)
-
15
@app = app
-
25
@condition = condition || lambda { true }
-
15
@validated = true
-
end
-
-
1
def call(env)
-
15
@validated = @condition.call
-
15
prepare!
-
-
15
response = @app.call(env)
-
24
response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
-
-
14
response
-
rescue Exception
-
1
cleanup!
-
1
raise
-
end
-
-
1
def prepare! #:nodoc:
-
18
run_callbacks :prepare if validated?
-
end
-
-
1
def cleanup! #:nodoc:
-
13
run_callbacks :cleanup if validated?
-
ensure
-
13
@validated = true
-
end
-
-
1
private
-
-
1
def validated? #:nodoc:
-
31
@validated
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
class RemoteIp
-
1
class IpSpoofAttackError < StandardError ; end
-
-
# IP addresses that are "trusted proxies" that can be stripped from
-
# the comma-delimited list in the X-Forwarded-For header. See also:
-
# http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
-
# http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses.
-
1
TRUSTED_PROXIES = %r{
-
^127\.0\.0\.1$ | # localhost
-
^::1$ |
-
^(10 | # private IP 10.x.x.x
-
172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
-
192\.168 | # private IP 192.168.x.x
-
fc00:: # private IP fc00
-
)\.
-
}x
-
-
1
attr_reader :check_ip, :proxies
-
-
1
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
-
158
@app = app
-
158
@check_ip = check_ip_spoofing
-
158
@proxies = case custom_proxies
-
when Regexp
-
4
custom_proxies
-
when nil
-
144
TRUSTED_PROXIES
-
else
-
10
Regexp.union(TRUSTED_PROXIES, custom_proxies)
-
end
-
end
-
-
1
def call(env)
-
158
env["action_dispatch.remote_ip"] = GetIp.new(env, self)
-
158
@app.call(env)
-
end
-
-
1
class GetIp
-
-
# IP v4 and v6 (with compression) validation regexp
-
# https://gist.github.com/1289635
-
1
VALID_IP = %r{
-
(^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
-
(^(
-
(([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
-
(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
-
(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
-
(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
-
(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
-
(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
-
(([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
-
(([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
-
(([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
-
(([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
-
(([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
-
(([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
-
(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
-
([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
-
(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
-
(([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
-
)$)
-
}x
-
-
1
def initialize(env, middleware)
-
158
@env = env
-
158
@middleware = middleware
-
158
@ip = nil
-
end
-
-
# Determines originating IP address. REMOTE_ADDR is the standard
-
# but will be wrong if the user is behind a proxy. Proxies will set
-
# HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
-
# HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
-
# multiple chained proxies. The first address which is in this list
-
# if it's not a known proxy will be the originating IP.
-
# Format of HTTP_X_FORWARDED_FOR:
-
# client_ip, proxy_ip1, proxy_ip2...
-
# http://en.wikipedia.org/wiki/X-Forwarded-For
-
1
def calculate_ip
-
46
client_ip = @env['HTTP_CLIENT_IP']
-
46
forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first
-
46
remote_addrs = ips_from('REMOTE_ADDR')
-
-
46
check_ip = client_ip && @middleware.check_ip
-
46
if check_ip && forwarded_ip != client_ip
-
# We don't know which came from the proxy, and which from the user
-
2
raise IpSpoofAttackError, "IP spoofing attack?!" \
-
"HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
-
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
-
end
-
-
44
client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten
-
44
if client_ips.present?
-
22
client_ips.first
-
else
-
# If there is no client ip we can return first valid proxy ip from REMOTE_ADDR
-
25
remote_addrs.find { |ip| valid_ip? ip }
-
end
-
end
-
-
1
def to_s
-
46
@ip ||= calculate_ip
-
end
-
-
1
private
-
-
1
def ips_from(header)
-
92
@env[header] ? @env[header].strip.split(/[,\s]+/) : []
-
end
-
-
1
def valid_ip?(ip)
-
114
ip =~ VALID_IP
-
end
-
-
1
def not_a_proxy?(ip)
-
57
ip !~ @middleware.proxies
-
end
-
-
1
def remove_proxies(ips)
-
155
ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) }
-
end
-
-
end
-
-
end
-
end
-
1
require 'securerandom'
-
1
require 'active_support/core_ext/string/access'
-
-
1
module ActionDispatch
-
# Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
-
# ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
-
#
-
# The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
-
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
-
# header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
-
#
-
# The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
-
# from multiple pieces of the stack.
-
1
class RequestId
-
1
def initialize(app)
-
6
@app = app
-
end
-
-
1
def call(env)
-
6
env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
-
12
@app.call(env).tap { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
-
end
-
-
1
private
-
1
def external_request_id(env)
-
6
if request_id = env["HTTP_X_REQUEST_ID"].presence
-
4
request_id.gsub(/[^\w\-]/, "").first(255)
-
end
-
end
-
-
1
def internal_request_id
-
2
SecureRandom.uuid
-
end
-
end
-
end
-
1
require 'rack/utils'
-
1
require 'rack/request'
-
1
require 'rack/session/abstract/id'
-
1
require 'action_dispatch/middleware/cookies'
-
1
require 'action_dispatch/request/session'
-
-
1
module ActionDispatch
-
1
module Session
-
1
class SessionRestoreError < StandardError #:nodoc:
-
1
attr_reader :original_exception
-
-
1
def initialize(const_error)
-
@original_exception = const_error
-
-
super("Session contains objects whose class definition isn't available.\n" +
-
"Remember to require the classes for all objects kept in the session.\n" +
-
"(Original exception: #{const_error.message} [#{const_error.class}])\n")
-
end
-
end
-
-
1
module Compatibility
-
1
def initialize(app, options = {})
-
48
options[:key] ||= '_session_id'
-
48
super
-
end
-
-
1
def generate_sid
-
41
sid = SecureRandom.hex(16)
-
41
sid.encode!('UTF-8')
-
41
sid
-
end
-
-
1
protected
-
-
1
def initialize_sid
-
48
@default_options.delete(:sidbits)
-
48
@default_options.delete(:secure_random)
-
end
-
end
-
-
1
module StaleSessionCheck
-
1
def load_session(env)
-
138
stale_session_check! { super }
-
end
-
-
1
def extract_session_id(env)
-
124
stale_session_check! { super }
-
end
-
-
1
def stale_session_check!
-
172
yield
-
rescue ArgumentError => argument_error
-
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
-
begin
-
# Note that the regexp does not allow $1 to end with a ':'
-
$1.constantize
-
rescue LoadError, NameError => e
-
raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace
-
end
-
retry
-
else
-
raise
-
end
-
end
-
end
-
-
1
module SessionObject # :nodoc:
-
1
def prepare_session(env)
-
86
Request::Session.create(self, env, @default_options)
-
end
-
-
1
def loaded_session?(session)
-
147
!session.is_a?(Request::Session) || session.loaded?
-
end
-
end
-
-
1
class AbstractStore < Rack::Session::Abstract::ID
-
1
include Compatibility
-
1
include StaleSessionCheck
-
1
include SessionObject
-
-
1
private
-
-
1
def set_cookie(env, session_id, cookie)
-
15
request = ActionDispatch::Request.new(env)
-
15
request.cookie_jar[key] = cookie
-
end
-
end
-
end
-
end
-
1
require 'action_dispatch/middleware/session/abstract_store'
-
-
1
module ActionDispatch
-
1
module Session
-
# Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
-
# if you don't store critical data in your sessions and you don't need them to live for extended periods
-
# of time.
-
1
class CacheStore < AbstractStore
-
# Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
-
# not specified, <tt>Rails.cache</tt> will be used.
-
1
def initialize(app, options = {})
-
9
@cache = options[:cache] || Rails.cache
-
9
options[:expire_after] ||= @cache.options[:expires_in]
-
9
super
-
end
-
-
# Get a session from the cache.
-
1
def get_session(env, sid)
-
16
sid ||= generate_sid
-
16
session = @cache.read(cache_key(sid))
-
16
session ||= {}
-
16
[sid, session]
-
end
-
-
# Set a session in the cache.
-
1
def set_session(env, sid, session, options)
-
14
key = cache_key(sid)
-
14
if session
-
14
@cache.write(key, session, :expires_in => options[:expire_after])
-
else
-
@cache.delete(key)
-
end
-
14
sid
-
end
-
-
# Remove a session from the cache.
-
1
def destroy_session(env, sid, options)
-
2
@cache.delete(cache_key(sid))
-
2
generate_sid
-
end
-
-
1
private
-
# Turn the session id into a cache key.
-
1
def cache_key(sid)
-
32
"_session_id:#{sid}"
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'action_dispatch/middleware/session/abstract_store'
-
1
require 'rack/session/cookie'
-
-
1
module ActionDispatch
-
1
module Session
-
# This cookie-based session store is the Rails default. Sessions typically
-
# contain at most a user_id and flash message; both fit within the 4K cookie
-
# size limit. Cookie-based sessions are dramatically faster than the
-
# alternatives.
-
#
-
# If you have more than 4K of session data or don't want your data to be
-
# visible to the user, pick another session store.
-
#
-
# CookieOverflow is raised if you attempt to store more than 4K of data.
-
#
-
# A message digest is included with the cookie to ensure data integrity:
-
# a user cannot alter his +user_id+ without knowing the secret key
-
# included in the hash. New apps are generated with a pregenerated secret
-
# in config/environment.rb. Set your own for old apps you're upgrading.
-
#
-
# Session options:
-
#
-
# * <tt>:secret</tt>: An application-wide key string or block returning a
-
# string called per generated digest. The block is called with the
-
# CGI::Session instance as an argument. It's important that the secret
-
# is not vulnerable to a dictionary attack. Therefore, you should choose
-
# a secret consisting of random numbers and letters and more than 30
-
# characters.
-
#
-
# secret: '449fe2e7daee471bffae2fd8dc02313d'
-
# secret: Proc.new { User.current_user.secret_key }
-
#
-
# * <tt>:digest</tt>: The message digest algorithm used to verify session
-
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
-
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
-
#
-
# To generate a secret key for an existing application, run
-
# "rake secret" and set the key in config/initializers/secret_token.rb.
-
#
-
# Note that changing digest or secret invalidates all existing sessions!
-
1
class CookieStore < Rack::Session::Cookie
-
1
include Compatibility
-
1
include StaleSessionCheck
-
1
include SessionObject
-
-
# Override rack's method
-
1
def destroy_session(env, session_id, options)
-
3
new_sid = super
-
# Reset hash and Assign the new session id
-
3
env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
-
3
new_sid
-
end
-
-
1
private
-
-
1
def unpacked_cookie_data(env)
-
57
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
-
41
stale_session_check! do
-
41
request = ActionDispatch::Request.new(env)
-
41
if data = request.cookie_jar.signed[@key]
-
21
data.stringify_keys!
-
end
-
41
data || {}
-
end
-
end
-
end
-
-
1
def set_session(env, sid, session_data, options)
-
33
session_data["session_id"] = sid
-
33
session_data
-
end
-
-
1
def set_cookie(env, session_id, cookie)
-
33
request = ActionDispatch::Request.new(env)
-
33
request.cookie_jar.signed[@key] = cookie
-
end
-
end
-
end
-
end
-
1
require 'action_dispatch/middleware/session/abstract_store'
-
1
begin
-
1
require 'rack/session/dalli'
-
rescue LoadError => e
-
$stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
-
raise e
-
end
-
-
1
module ActionDispatch
-
1
module Session
-
1
class MemCacheStore < Rack::Session::Dalli
-
1
include Compatibility
-
1
include StaleSessionCheck
-
1
include SessionObject
-
-
1
def initialize(app, options = {})
-
9
options[:expire_after] ||= options[:expires]
-
9
super
-
end
-
end
-
end
-
end
-
1
require 'action_dispatch/http/request'
-
1
require 'action_dispatch/middleware/exception_wrapper'
-
-
1
module ActionDispatch
-
# This middleware rescues any exception returned by the application
-
# and calls an exceptions app that will wrap it in a format for the end user.
-
#
-
# The exceptions app should be passed as parameter on initialization
-
# of ShowExceptions. Every time there is an exception, ShowExceptions will
-
# store the exception in env["action_dispatch.exception"], rewrite the
-
# PATH_INFO to the exception status code and call the rack app.
-
#
-
# If the application returns a "X-Cascade" pass response, this middleware
-
# will send an empty response as result with the correct status code.
-
# If any exception happens inside the exceptions app, this middleware
-
# catches the exceptions and returns a FAILSAFE_RESPONSE.
-
1
class ShowExceptions
-
1
FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' },
-
["500 Internal Server Error\n" <<
-
"If you are the administrator of this website, then please read this web " <<
-
"application's log file and/or the web server's log file to find out what " <<
-
"went wrong."]]
-
-
1
def initialize(app, exceptions_app)
-
161
@app = app
-
161
@exceptions_app = exceptions_app
-
end
-
-
1
def call(env)
-
230
begin
-
230
response = @app.call(env)
-
rescue Exception => exception
-
32
raise exception if env['action_dispatch.show_exceptions'] == false
-
end
-
-
221
response || render_exception(env, exception)
-
end
-
-
1
private
-
-
1
def render_exception(env, exception)
-
23
wrapper = ExceptionWrapper.new(env, exception)
-
23
status = wrapper.status_code
-
23
env["action_dispatch.exception"] = wrapper.exception
-
23
env["PATH_INFO"] = "/#{status}"
-
23
response = @exceptions_app.call(env)
-
22
response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
-
rescue Exception => failsafe_error
-
1
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
-
1
FAILSAFE_RESPONSE
-
end
-
-
1
def pass_response(status)
-
8
[status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []]
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
class SSL
-
1
YEAR = 31536000
-
-
1
def self.default_hsts_options
-
17
{ :expires => YEAR, :subdomains => false }
-
end
-
-
1
def initialize(app, options = {})
-
18
@app = app
-
-
18
@hsts = options.fetch(:hsts, {})
-
18
@hsts = {} if @hsts == true
-
18
@hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
-
-
18
@host = options[:host]
-
18
@port = options[:port]
-
end
-
-
1
def call(env)
-
18
request = Request.new(env)
-
-
18
if request.ssl?
-
12
status, headers, body = @app.call(env)
-
12
headers = hsts_headers.merge(headers)
-
12
flag_cookies_as_secure!(headers)
-
12
[status, headers, body]
-
else
-
6
redirect_to_https(request)
-
end
-
end
-
-
1
private
-
1
def redirect_to_https(request)
-
6
url = URI(request.url)
-
6
url.scheme = "https"
-
6
url.host = @host if @host
-
6
url.port = @port if @port
-
6
headers = hsts_headers.merge('Content-Type' => 'text/html',
-
'Location' => url.to_s)
-
-
6
[301, headers, []]
-
end
-
-
# http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
-
1
def hsts_headers
-
18
if @hsts
-
17
value = "max-age=#{@hsts[:expires]}"
-
17
value += "; includeSubDomains" if @hsts[:subdomains]
-
17
{ 'Strict-Transport-Security' => value }
-
else
-
1
{}
-
end
-
end
-
-
1
def flag_cookies_as_secure!(headers)
-
12
if cookies = headers['Set-Cookie']
-
11
cookies = cookies.split("\n")
-
-
11
headers['Set-Cookie'] = cookies.map { |cookie|
-
19
if cookie !~ /;\s+secure(;|$)/
-
8
"#{cookie}; secure"
-
else
-
11
cookie
-
end
-
}.join("\n")
-
end
-
end
-
end
-
end
-
1
require "active_support/inflector/methods"
-
1
require "active_support/dependencies"
-
-
1
module ActionDispatch
-
1
class MiddlewareStack
-
1
class Middleware
-
1
attr_reader :args, :block, :name, :classcache
-
-
1
def initialize(klass_or_name, *args, &block)
-
1482
@klass = nil
-
-
1482
if klass_or_name.respond_to?(:name)
-
109
@klass = klass_or_name
-
109
@name = @klass.name
-
else
-
1373
@name = klass_or_name.to_s
-
end
-
-
1482
@classcache = ActiveSupport::Dependencies::Reference
-
1482
@args, @block = args, block
-
end
-
-
1
def klass
-
1454
@klass || classcache[@name]
-
end
-
-
1
def ==(middleware)
-
396
case middleware
-
when Middleware
-
2
klass == middleware.klass
-
when Class
-
11
klass == middleware
-
else
-
383
normalize(@name) == normalize(middleware)
-
end
-
end
-
-
1
def inspect
-
klass.to_s
-
end
-
-
1
def build(app)
-
1418
klass.new(app, *args, &block)
-
end
-
-
1
private
-
-
1
def normalize(object)
-
766
object.to_s.strip.sub(/^::/, '')
-
end
-
end
-
-
1
include Enumerable
-
-
1
attr_accessor :middlewares
-
-
1
def initialize(*args)
-
211
@middlewares = []
-
211
yield(self) if block_given?
-
end
-
-
1
def each
-
7
@middlewares.each { |x| yield x }
-
end
-
-
1
def size
-
12
middlewares.size
-
end
-
-
1
def last
-
8
middlewares.last
-
end
-
-
1
def [](i)
-
8
middlewares[i]
-
end
-
-
1
def unshift(*args, &block)
-
1
middleware = self.class::Middleware.new(*args, &block)
-
1
middlewares.unshift(middleware)
-
end
-
-
1
def initialize_copy(other)
-
318
self.middlewares = other.middlewares.dup
-
end
-
-
1
def insert(index, *args, &block)
-
9
index = assert_index(index, :before)
-
8
middleware = self.class::Middleware.new(*args, &block)
-
8
middlewares.insert(index, middleware)
-
end
-
-
1
alias_method :insert_before, :insert
-
-
1
def insert_after(index, *args, &block)
-
3
index = assert_index(index, :after)
-
2
insert(index + 1, *args, &block)
-
end
-
-
1
def swap(target, *args, &block)
-
2
index = assert_index(target, :before)
-
2
insert(index, *args, &block)
-
2
middlewares.delete_at(index + 1)
-
end
-
-
1
def delete(target)
-
46
middlewares.delete target
-
end
-
-
1
def use(*args, &block)
-
1461
middleware = self.class::Middleware.new(*args, &block)
-
1461
middlewares.push(middleware)
-
end
-
-
1
def build(app = nil, &block)
-
195
app ||= block
-
195
raise "MiddlewareStack#build requires an app" unless app
-
1567
middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
-
end
-
-
1
protected
-
-
1
def assert_index(index, where)
-
14
i = index.is_a?(Integer) ? index : middlewares.index(index)
-
14
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
-
12
i
-
end
-
end
-
end
-
1
require 'rack/utils'
-
1
require 'active_support/core_ext/uri'
-
-
1
module ActionDispatch
-
1
class FileHandler
-
1
def initialize(root, cache_control)
-
1
@root = root.chomp('/')
-
1
@compiled_root = /^#{Regexp.escape(root)}/
-
1
@file_server = ::Rack::File.new(@root, cache_control)
-
end
-
-
1
def match?(path)
-
34
path = path.dup
-
-
34
full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
-
34
paths = "#{full_path}#{ext}"
-
-
34
matches = Dir[paths]
-
70
match = matches.detect { |m| File.file?(m) }
-
34
if match
-
32
match.sub!(@compiled_root, '')
-
32
::Rack::Utils.escape(match)
-
end
-
end
-
-
1
def call(env)
-
32
@file_server.call(env)
-
end
-
-
1
def ext
-
@ext ||= begin
-
1
ext = ::ActionController::Base.default_static_extension
-
1
"{,#{ext},/index#{ext}}"
-
34
end
-
end
-
-
1
def unescape_path(path)
-
32
URI.parser.unescape(path)
-
end
-
-
1
def escape_glob_chars(path)
-
32
path.force_encoding('binary') if path.respond_to? :force_encoding
-
32
path.gsub(/[*?{}\[\]]/, "\\\\\\&")
-
end
-
end
-
-
1
class Static
-
1
def initialize(app, path, cache_control=nil)
-
1
@app = app
-
1
@file_handler = FileHandler.new(path, cache_control)
-
end
-
-
1
def call(env)
-
34
case env['REQUEST_METHOD']
-
when 'GET', 'HEAD'
-
34
path = env['PATH_INFO'].chomp('/')
-
34
if match = @file_handler.match?(path)
-
32
env["PATH_INFO"] = match
-
32
return @file_handler.call(env)
-
end
-
end
-
-
2
@app.call(env)
-
end
-
end
-
end
-
1
require 'rack/session/abstract/id'
-
-
1
module ActionDispatch
-
1
class Request < Rack::Request
-
# Session is responsible for lazily loading the session from store.
-
1
class Session # :nodoc:
-
1
ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
-
1
ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:
-
-
1
def self.create(store, env, default_options)
-
94
session_was = find env
-
94
session = Request::Session.new(store, env)
-
94
session.merge! session_was if session_was
-
-
94
set(env, session)
-
94
Options.set(env, Request::Session::Options.new(store, env, default_options))
-
94
session
-
end
-
-
1
def self.find(env)
-
394
env[ENV_SESSION_KEY]
-
end
-
-
1
def self.set(env, session)
-
4382
env[ENV_SESSION_KEY] = session
-
end
-
-
1
class Options #:nodoc:
-
1
def self.set(env, options)
-
4369
env[ENV_SESSION_OPTIONS_KEY] = options
-
end
-
-
1
def self.find(env)
-
81
env[ENV_SESSION_OPTIONS_KEY]
-
end
-
-
1
def initialize(by, env, default_options)
-
94
@by = by
-
94
@env = env
-
94
@delegate = default_options.dup
-
end
-
-
1
def [](key)
-
625
if key == :id
-
149
@delegate.fetch(key) {
-
62
@delegate[:id] = @by.send(:extract_session_id, @env)
-
}
-
else
-
476
@delegate[key]
-
end
-
end
-
-
84
def []=(k,v); @delegate[k] = v; end
-
62
def to_hash; @delegate.dup; end
-
27
def values_at(*args); @delegate.values_at(*args); end
-
end
-
-
1
def initialize(by, env)
-
94
@by = by
-
94
@env = env
-
94
@delegate = {}
-
94
@loaded = false
-
94
@exists = nil # we haven't checked yet
-
end
-
-
1
def options
-
81
Options.find @env
-
end
-
-
1
def destroy
-
6
clear
-
6
options = self.options || {}
-
6
new_sid = @by.send(:destroy_session, @env, options[:id], options)
-
6
options[:id] = new_sid # Reset session id with a new value or nil
-
-
# Load the new sid to be written with the response
-
6
@loaded = false
-
6
load_for_write!
-
end
-
-
1
def [](key)
-
51
load_for_read!
-
51
@delegate[key.to_s]
-
end
-
-
1
def has_key?(key)
-
70
load_for_read!
-
70
@delegate.key?(key.to_s)
-
end
-
1
alias :key? :has_key?
-
1
alias :include? :has_key?
-
-
1
def keys
-
1
@delegate.keys
-
end
-
-
1
def values
-
2
@delegate.values
-
end
-
-
1
def []=(key, value)
-
54
load_for_write!
-
54
@delegate[key.to_s] = value
-
end
-
-
1
def clear
-
8
load_for_write!
-
8
@delegate.clear
-
end
-
-
1
def to_hash
-
78
load_for_read!
-
191
@delegate.dup.delete_if { |_,v| v.nil? }
-
end
-
-
1
def update(hash)
-
load_for_write!
-
@delegate.update stringify_keys(hash)
-
end
-
-
1
def delete(key)
-
load_for_write!
-
@delegate.delete key.to_s
-
end
-
-
1
def inspect
-
if loaded?
-
super
-
else
-
"#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>"
-
end
-
end
-
-
1
def exists?
-
39
return @exists unless @exists.nil?
-
39
@exists = @by.send(:session_exists?, @env)
-
end
-
-
1
def loaded?
-
509
@loaded
-
end
-
-
1
def empty?
-
2
load_for_read!
-
2
@delegate.empty?
-
end
-
-
1
def merge!(other)
-
2
load_for_write!
-
2
@delegate.merge!(other)
-
end
-
-
1
private
-
-
1
def load_for_read!
-
201
load! if !loaded? && exists?
-
end
-
-
1
def load_for_write!
-
70
load! unless loaded?
-
end
-
-
1
def load!
-
75
id, session = @by.load_session @env
-
75
options[:id] = id
-
75
@delegate.replace(stringify_keys(session))
-
75
@loaded = true
-
end
-
-
1
def stringify_keys(other)
-
75
other.each_with_object({}) { |(key, value), hash|
-
61
hash[key.to_s] = value
-
}
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
1
require 'active_support/core_ext/object/to_param'
-
1
require 'active_support/core_ext/regexp'
-
-
1
module ActionDispatch
-
# The routing module provides URL rewriting in native Ruby. It's a way to
-
# redirect incoming requests to controllers and actions. This replaces
-
# mod_rewrite rules. Best of all, Rails' \Routing works with any web server.
-
# Routes are defined in <tt>config/routes.rb</tt>.
-
#
-
# Think of creating routes as drawing a map for your requests. The map tells
-
# them where to go based on some predefined pattern:
-
#
-
# AppName::Application.routes.draw do
-
# Pattern 1 tells some request to go to one place
-
# Pattern 2 tell them to go to another
-
# ...
-
# end
-
#
-
# The following symbols are special:
-
#
-
# :controller maps to your controller name
-
# :action maps to an action with your controllers
-
#
-
# Other names simply map to a parameter as in the case of <tt>:id</tt>.
-
#
-
# == Resources
-
#
-
# Resource routing allows you to quickly declare all of the common routes
-
# for a given resourceful controller. Instead of declaring separate routes
-
# for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+
-
# actions, a resourceful route declares them in a single line of code:
-
#
-
# resources :photos
-
#
-
# Sometimes, you have a resource that clients always look up without
-
# referencing an ID. A common example, /profile always shows the profile of
-
# the currently logged in user. In this case, you can use a singular resource
-
# to map /profile (rather than /profile/:id) to the show action.
-
#
-
# resource :profile
-
#
-
# It's common to have resources that are logically children of other
-
# resources:
-
#
-
# resources :magazines do
-
# resources :ads
-
# end
-
#
-
# You may wish to organize groups of controllers under a namespace. Most
-
# commonly, you might group a number of administrative controllers under
-
# an +admin+ namespace. You would place these controllers under the
-
# <tt>app/controllers/admin</tt> directory, and you can group them together
-
# in your router:
-
#
-
# namespace "admin" do
-
# resources :posts, :comments
-
# end
-
#
-
# Alternately, you can add prefixes to your path without using a separate
-
# directory by using +scope+. +scope+ takes additional options which
-
# apply to all enclosed routes.
-
#
-
# scope path: "/cpanel", as: 'admin' do
-
# resources :posts, :comments
-
# end
-
#
-
# For more, see <tt>Routing::Mapper::Resources#resources</tt>,
-
# <tt>Routing::Mapper::Scoping#namespace</tt>, and
-
# <tt>Routing::Mapper::Scoping#scope</tt>.
-
#
-
# == Named routes
-
#
-
# Routes can be named by passing an <tt>:as</tt> option,
-
# allowing for easy reference within your source as +name_of_route_url+
-
# for the full URL and +name_of_route_path+ for the URI path.
-
#
-
# Example:
-
#
-
# # In routes.rb
-
# match '/login' => 'accounts#login', as: 'login'
-
#
-
# # With render, redirect_to, tests, etc.
-
# redirect_to login_url
-
#
-
# Arguments can be passed as well.
-
#
-
# redirect_to show_item_path(id: 25)
-
#
-
# Use <tt>root</tt> as a shorthand to name a route for the root path "/".
-
#
-
# # In routes.rb
-
# root to: 'blogs#index'
-
#
-
# # would recognize http://www.example.com/ as
-
# params = { controller: 'blogs', action: 'index' }
-
#
-
# # and provide these named routes
-
# root_url # => 'http://www.example.com/'
-
# root_path # => '/'
-
#
-
# Note: when using +controller+, the route is simply named after the
-
# method you call on the block parameter rather than map.
-
#
-
# # In routes.rb
-
# controller :blog do
-
# match 'blog/show' => :list
-
# match 'blog/delete' => :delete
-
# match 'blog/edit/:id' => :edit
-
# end
-
#
-
# # provides named routes for show, delete, and edit
-
# link_to @article.title, show_path(id: @article.id)
-
#
-
# == Pretty URLs
-
#
-
# Routes can generate pretty URLs. For example:
-
#
-
# match '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
-
# year: /\d{4}/,
-
# month: /\d{1,2}/,
-
# day: /\d{1,2}/
-
# }
-
#
-
# Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
-
# maps to
-
#
-
# params = {year: '2005', month: '11', day: '06'}
-
#
-
# == Regular Expressions and parameters
-
# You can specify a regular expression to define a format for a parameter.
-
#
-
# controller 'geocode' do
-
# match 'geocode/:postalcode' => :show, constraints: {
-
# postalcode: /\d{5}(-\d{4})?/
-
# }
-
#
-
# Constraints can include the 'ignorecase' and 'extended syntax' regular
-
# expression modifiers:
-
#
-
# controller 'geocode' do
-
# match 'geocode/:postalcode' => :show, constraints: {
-
# postalcode: /hx\d\d\s\d[a-z]{2}/i
-
# }
-
# end
-
#
-
# controller 'geocode' do
-
# match 'geocode/:postalcode' => :show, constraints: {
-
# postalcode: /# Postcode format
-
# \d{5} #Prefix
-
# (-\d{4})? #Suffix
-
# /x
-
# }
-
# end
-
#
-
# Using the multiline match modifier will raise an +ArgumentError+.
-
# Encoding regular expression modifiers are silently ignored. The
-
# match will always use the default encoding or ASCII.
-
#
-
# == Default route
-
#
-
# Consider the following route, which you will find commented out at the
-
# bottom of your generated <tt>config/routes.rb</tt>:
-
#
-
# match ':controller(/:action(/:id))(.:format)'
-
#
-
# This route states that it expects requests to consist of a
-
# <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
-
# turn is followed optionally by an <tt>:id</tt>, which in turn is followed
-
# optionally by a <tt>:format</tt>.
-
#
-
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
-
# up with:
-
#
-
# params = { controller: 'blog',
-
# action: 'edit',
-
# id: '22'
-
# }
-
#
-
# By not relying on default routes, you improve the security of your
-
# application since not all controller actions, which includes actions you
-
# might add at a later time, are exposed by default.
-
#
-
# == HTTP Methods
-
#
-
# Using the <tt>:via</tt> option when specifying a route allows you to
-
# restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
-
# <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
-
# <tt>:any</tt>. If your route needs to respond to more than one method you
-
# can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
-
# <tt>:any</tt> which means that the route will respond to any of the HTTP
-
# methods.
-
#
-
# Examples:
-
#
-
# match 'post/:id' => 'posts#show', via: :get
-
# match 'post/:id' => 'posts#create_comment', via: :post
-
#
-
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
-
# URL will route to the <tt>show</tt> action.
-
#
-
# === HTTP helper methods
-
#
-
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
-
# methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
-
#
-
# Examples:
-
#
-
# get 'post/:id' => 'posts#show'
-
# post 'post/:id' => 'posts#create_comment'
-
#
-
# This syntax is less verbose and the intention is more apparent to someone else reading your code,
-
# however if your route needs to respond to more than one HTTP method (or all methods) then using the
-
# <tt>:via</tt> option on <tt>match</tt> is preferable.
-
#
-
# == External redirects
-
#
-
# You can redirect any path to another path using the redirect helper in your router:
-
#
-
# match "/stories" => redirect("/posts")
-
#
-
# == Unicode character routes
-
#
-
# You can specify unicode character routes in your router:
-
#
-
# match "こんにちは" => "welcome#index"
-
#
-
# == Routing to Rack Applications
-
#
-
# Instead of a String, like <tt>posts#index</tt>, which corresponds to the
-
# index action in the PostsController, you can specify any Rack application
-
# as the endpoint for a matcher:
-
#
-
# match "/application.js" => Sprockets
-
#
-
# == Reloading routes
-
#
-
# You can reload routes if you feel you must:
-
#
-
# Rails.application.reload_routes!
-
#
-
# This will clear all named routes and reload routes.rb if the file has been modified from
-
# last load. To absolutely force reloading, use <tt>reload!</tt>.
-
#
-
# == Testing Routes
-
#
-
# The two main methods for testing your routes:
-
#
-
# === +assert_routing+
-
#
-
# def test_movie_route_properly_splits
-
# opts = {controller: "plugin", action: "checkout", id: "2"}
-
# assert_routing "plugin/checkout/2", opts
-
# end
-
#
-
# +assert_routing+ lets you test whether or not the route properly resolves into options.
-
#
-
# === +assert_recognizes+
-
#
-
# def test_route_has_options
-
# opts = {controller: "plugin", action: "show", id: "12"}
-
# assert_recognizes opts, "/plugins/show/12"
-
# end
-
#
-
# Note the subtle difference between the two: +assert_routing+ tests that
-
# a URL fits options while +assert_recognizes+ tests that a URL
-
# breaks into parameters properly.
-
#
-
# In tests you can simply pass the URL or named route to +get+ or +post+.
-
#
-
# def send_to_jail
-
# get '/jail'
-
# assert_response :success
-
# assert_template "jail/front"
-
# end
-
#
-
# def goes_to_login
-
# get login_url
-
# #...
-
# end
-
#
-
# == View a list of all your routes
-
#
-
# rake routes
-
#
-
# Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
-
#
-
1
module Routing
-
1
autoload :Mapper, 'action_dispatch/routing/mapper'
-
1
autoload :RouteSet, 'action_dispatch/routing/route_set'
-
1
autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy'
-
1
autoload :UrlFor, 'action_dispatch/routing/url_for'
-
1
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
-
-
1
SEPARATORS = %w( / . ? ) #:nodoc:
-
1
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/hash/reverse_merge'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/enumerable'
-
1
require 'active_support/inflector'
-
1
require 'action_dispatch/routing/redirection'
-
-
1
module ActionDispatch
-
1
module Routing
-
1
class Mapper
-
1
class Constraints #:nodoc:
-
1
def self.new(app, constraints, request = Rack::Request)
-
8230
if constraints.any?
-
251
super(app, constraints, request)
-
else
-
7979
app
-
end
-
end
-
-
1
attr_reader :app, :constraints
-
-
1
def initialize(app, constraints, request)
-
251
@app, @constraints, @request = app, constraints, request
-
end
-
-
1
def matches?(env)
-
23
req = @request.new(env)
-
-
23
@constraints.each { |constraint|
-
23
if constraint.respond_to?(:matches?) && !constraint.matches?(req)
-
4
return false
-
elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))
-
7
return false
-
end
-
}
-
-
12
return true
-
ensure
-
23
req.reset_parameters
-
end
-
-
1
def call(env)
-
16
matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
-
end
-
-
1
private
-
1
def constraint_args(constraint, request)
-
16
constraint.arity == 1 ? [request] : [request.symbolized_path_parameters, request]
-
end
-
end
-
-
1
class Mapping #:nodoc:
-
1
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
-
1
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
-
1
SHORTHAND_REGEX = %r{/[\w/]+$}
-
1
WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
-
-
1
def initialize(set, scope, path, options)
-
8242
@set, @scope = set, scope
-
8242
@segment_keys = nil
-
8242
@options = (@scope[:options] || {}).merge(options)
-
8242
@path = normalize_path(path)
-
8241
normalize_options!
-
-
8230
via_all = @options.delete(:via) if @options[:via] == :all
-
-
8230
if !via_all && request_method_condition.empty?
-
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
-
"If you want to expose your action to GET, use `get` in the router:\n\n" \
-
" Instead of: match \"controller#action\"\n" \
-
" Do: get \"controller#action\""
-
raise msg
-
end
-
end
-
-
1
def to_route
-
8230
[ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
-
end
-
-
1
private
-
-
1
def normalize_options!
-
8241
path_without_format = @path.sub(/\(\.:format\)$/, '')
-
-
8241
if using_match_shorthand?(path_without_format, @options)
-
7
to_shorthand = @options[:to].blank?
-
7
@options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1')
-
end
-
-
8241
@options.merge!(default_controller_and_action(to_shorthand))
-
-
8236
requirements.each do |name, requirement|
-
# segment_keys.include?(k.to_s) || k == :controller
-
1572
next unless Regexp === requirement && !constraints[name]
-
-
1367
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
-
5
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
-
end
-
1362
if requirement.multiline?
-
1
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
-
end
-
end
-
-
8230
if @options[:constraints].is_a?(Hash)
-
299
(@options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(@options[:constraints]))
-
end
-
end
-
-
# match "account/overview"
-
1
def using_match_shorthand?(path, options)
-
8241
path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
-
end
-
-
1
def normalize_path(path)
-
8242
raise ArgumentError, "path is required" if path.blank?
-
8242
path = Mapper.normalize_path(path)
-
-
8242
if path.match(':controller')
-
575
raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
-
-
# Add a default constraint for :controller path segments that matches namespaced
-
# controllers with default routes like :controller/:action/:id(.:format), e.g:
-
# GET /admin/products/show/1
-
# => { controller: 'admin/products', action: 'show', id: '1' }
-
574
@options[:controller] ||= /.+?/
-
end
-
-
# Add a constraint for wildcard route to make it non-greedy and match the
-
# optional format part of the route by default
-
8241
if path.match(WILDCARD_PATH) && @options[:format] != false
-
21
@options[$1.to_sym] ||= /.+?/
-
end
-
-
8241
if @options[:format] == false
-
16
@options.delete(:format)
-
16
path
-
8225
elsif path.include?(":format") || path.end_with?('/')
-
1227
path
-
6998
elsif @options[:format] == true
-
1
"#{path}.:format"
-
else
-
6997
"#{path}(.:format)"
-
end
-
end
-
-
1
def app
-
8230
Constraints.new(
-
to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
-
blocks,
-
@set.request_class
-
)
-
end
-
-
1
def conditions
-
8230
{ :path_info => @path }.merge(constraints).merge(request_method_condition)
-
end
-
-
1
def requirements
-
8236
@requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements|
-
8236
requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
-
50420
@options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
-
26033
end
-
end
-
-
1
def defaults
-
8230
@defaults ||= (@options[:defaults] || {}).tap do |defaults|
-
8230
defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
-
50563
@options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
-
15352
end
-
end
-
-
1
def default_controller_and_action(to_shorthand=nil)
-
8241
if to.respond_to?(:call)
-
1108
{ }
-
else
-
7133
if to.is_a?(String)
-
1513
controller, action = to.split('#')
-
elsif to.is_a?(Symbol)
-
11
action = to.to_s
-
end
-
-
7133
controller ||= default_controller
-
7133
action ||= default_action
-
-
7133
unless controller.is_a?(Regexp) || to_shorthand
-
6552
controller = [@scope[:module], controller].compact.join("/").presence
-
end
-
-
7133
if controller.is_a?(String) && controller =~ %r{\A/}
-
5
raise ArgumentError, "controller name should not start with a slash"
-
end
-
-
7128
controller = controller.to_s unless controller.is_a?(Regexp)
-
7128
action = action.to_s unless action.is_a?(Regexp)
-
-
7128
if controller.blank? && segment_keys.exclude?("controller")
-
raise ArgumentError, "missing :controller"
-
end
-
-
7128
if action.blank? && segment_keys.exclude?("action")
-
raise ArgumentError, "missing :action"
-
end
-
-
7128
hash = {}
-
7128
hash[:controller] = controller unless controller.blank?
-
7128
hash[:action] = action unless action.blank?
-
7128
hash
-
end
-
end
-
-
1
def blocks
-
8230
constraints = @options[:constraints]
-
8230
if constraints.present? && !constraints.is_a?(Hash)
-
7
[constraints]
-
else
-
8223
@scope[:blocks] || []
-
end
-
end
-
-
1
def constraints
-
11248
@constraints ||= requirements.reject { |k, v| segment_keys.include?(k.to_s) || k == :controller }
-
end
-
-
1
def request_method_condition
-
16428
if via = @options[:via]
-
33006
list = Array(via).map { |m| m.to_s.dasherize.upcase }
-
16396
{ :request_method => list }
-
else
-
32
{ }
-
end
-
end
-
-
1
def segment_keys
-
2069
return @segment_keys if @segment_keys
-
-
1331
@segment_keys = Journey::Path::Pattern.new(
-
Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
-
).names
-
end
-
-
1
def to
-
31856
@options[:to]
-
end
-
-
1
def default_controller
-
5620
@options[:controller] || @scope[:controller]
-
end
-
-
1
def default_action
-
5627
@options[:action] || @scope[:action]
-
end
-
-
1
def defaults_from_constraints(constraints)
-
299
url_keys = [:protocol, :subdomain, :domain, :host, :port]
-
305
constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
-
end
-
end
-
-
# Invokes Rack::Mount::Utils.normalize path and ensure that
-
# (:locale) becomes (/:locale) instead of /(:locale). Except
-
# for root cases, where the latter is the correct one.
-
1
def self.normalize_path(path)
-
14934
path = Journey::Router::Utils.normalize_path(path)
-
14934
path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^)]+\)$}
-
14934
path
-
end
-
-
1
def self.normalize_name(name)
-
4099
normalize_path(name)[1..-1].tr("/", "_")
-
end
-
-
1
module Base
-
# You can specify what Rails should route "/" to with the root method:
-
#
-
# root to: 'pages#main'
-
#
-
# For options, see +match+, as +root+ uses it internally.
-
#
-
# You can also pass a string which will expand
-
#
-
# root 'pages#main'
-
#
-
# You should put the root route at the top of <tt>config/routes.rb</tt>,
-
# because this means it will be matched first. As this is the most popular route
-
# of most Rails applications, this is beneficial.
-
1
def root(options = {})
-
201
options = { :to => options } if options.is_a?(String)
-
201
match '/', { :as => :root, :via => :get }.merge(options)
-
end
-
-
# Matches a url pattern to one or more routes. Any symbols in a pattern
-
# are interpreted as url query parameters and thus available as +params+
-
# in an action:
-
#
-
# # sets :controller, :action and :id in params
-
# match ':controller/:action/:id'
-
#
-
# Two of these symbols are special, +:controller+ maps to the controller
-
# and +:action+ to the controller's action. A pattern can also map
-
# wildcard segments (globs) to params:
-
#
-
# match 'songs/*category/:title' => 'songs#show'
-
#
-
# # 'songs/rock/classic/stairway-to-heaven' sets
-
# # params[:category] = 'rock/classic'
-
# # params[:title] = 'stairway-to-heaven'
-
#
-
# When a pattern points to an internal route, the route's +:action+ and
-
# +:controller+ should be set in options or hash shorthand. Examples:
-
#
-
# match 'photos/:id' => 'photos#show'
-
# match 'photos/:id', to: 'photos#show'
-
# match 'photos/:id', controller: 'photos', action: 'show'
-
#
-
# A pattern can also point to a +Rack+ endpoint i.e. anything that
-
# responds to +call+:
-
#
-
# match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] }
-
# match 'photos/:id' => PhotoRackApp
-
# # Yes, controller actions are just rack endpoints
-
# match 'photos/:id' => PhotosController.action(:show)
-
#
-
# === Options
-
#
-
# Any options not seen here are passed on as params with the url.
-
#
-
# [:controller]
-
# The route's controller.
-
#
-
# [:action]
-
# The route's action.
-
#
-
# [:path]
-
# The path prefix for the routes.
-
#
-
# [:module]
-
# The namespace for :controller.
-
#
-
# match 'path' => 'c#a', module: 'sekret', controller: 'posts'
-
# #=> Sekret::PostsController
-
#
-
# See <tt>Scoping#namespace</tt> for its scope equivalent.
-
#
-
# [:as]
-
# The name used to generate routing helpers.
-
#
-
# [:via]
-
# Allowed HTTP verb(s) for route.
-
#
-
# match 'path' => 'c#a', via: :get
-
# match 'path' => 'c#a', via: [:get, :post]
-
#
-
# [:to]
-
# Points to a +Rack+ endpoint. Can be an object that responds to
-
# +call+ or a string representing a controller's action.
-
#
-
# match 'path', to: 'controller#action'
-
# match 'path', to: lambda { |env| [200, {}, "Success!"] }
-
# match 'path', to: RackApp
-
#
-
# [:on]
-
# Shorthand for wrapping routes in a specific RESTful context. Valid
-
# values are +:member+, +:collection+, and +:new+. Only use within
-
# <tt>resource(s)</tt> block. For example:
-
#
-
# resource :bar do
-
# match 'foo' => 'c#a', on: :member, via: [:get, :post]
-
# end
-
#
-
# Is equivalent to:
-
#
-
# resource :bar do
-
# member do
-
# match 'foo' => 'c#a', via: [:get, :post]
-
# end
-
# end
-
#
-
# [:constraints]
-
# Constrains parameters with a hash of regular expressions or an
-
# object that responds to <tt>matches?</tt>
-
#
-
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }
-
#
-
# class Blacklist
-
# def matches?(request) request.remote_ip == '1.2.3.4' end
-
# end
-
# match 'path' => 'c#a', constraints: Blacklist.new
-
#
-
# See <tt>Scoping#constraints</tt> for more examples with its scope
-
# equivalent.
-
#
-
# [:defaults]
-
# Sets defaults for parameters
-
#
-
# # Sets params[:format] to 'jpg' by default
-
# match 'path' => 'c#a', defaults: { format: 'jpg' }
-
#
-
# See <tt>Scoping#defaults</tt> for its scope equivalent.
-
#
-
# [:anchor]
-
# Boolean to anchor a <tt>match</tt> pattern. Default is true. When set to
-
# false, the pattern matches any request prefixed with the given path.
-
#
-
# # Matches any request starting with 'path'
-
# match 'path' => 'c#a', anchor: false
-
#
-
# [:format]
-
# Allows you to specify the default value for optional +format+
-
# segment or disable it by supplying +false+.
-
1
def match(path, options=nil)
-
end
-
-
# Mount a Rack-based application to be used within the application.
-
#
-
# mount SomeRackApp, at: "some_route"
-
#
-
# Alternatively:
-
#
-
# mount(SomeRackApp => "some_route")
-
#
-
# For options, see +match+, as +mount+ uses it internally.
-
#
-
# All mounted applications come with routing helpers to access them.
-
# These are named after the class specified, so for the above example
-
# the helper is either +some_rack_app_path+ or +some_rack_app_url+.
-
# To customize this helper's name, use the +:as+ option:
-
#
-
# mount(SomeRackApp => "some_route", as: "exciting")
-
#
-
# This will generate the +exciting_path+ and +exciting_url+ helpers
-
# which can be used to navigate to this mounted app.
-
1
def mount(app, options = nil)
-
14
if options
-
5
path = options.delete(:at)
-
else
-
9
unless Hash === app
-
1
raise ArgumentError, "must be called with mount point"
-
end
-
-
8
options = app
-
16
app, path = options.find { |k, v| k.respond_to?(:call) }
-
8
options.delete(app) if app
-
end
-
-
13
raise "A rack application must be specified" unless path
-
-
13
options[:as] ||= app_name(app)
-
13
options[:via] ||= :all
-
-
13
match(path, options.merge(:to => app, :anchor => false, :format => false))
-
-
13
define_generate_prefix(app, options[:as])
-
13
self
-
end
-
-
1
def default_url_options=(options)
-
2
@set.default_url_options = options
-
end
-
1
alias_method :default_url_options, :default_url_options=
-
-
1
def with_default_scope(scope, &block)
-
1
scope(scope) do
-
1
instance_exec(&block)
-
end
-
end
-
-
1
private
-
1
def app_name(app)
-
9
return unless app.respond_to?(:routes)
-
-
3
if app.respond_to?(:railtie_name)
-
app.railtie_name
-
else
-
3
class_name = app.class.is_a?(Class) ? app.name : app.class.name
-
3
ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
-
end
-
end
-
-
1
def define_generate_prefix(app, name)
-
13
return unless app.respond_to?(:routes) && app.routes.respond_to?(:define_mounted_helper)
-
-
5
_route = @set.named_routes.routes[name.to_sym]
-
5
_routes = @set
-
5
app.routes.define_mounted_helper(name)
-
5
app.routes.singleton_class.class_eval do
-
5
define_method :mounted? do
-
4
true
-
end
-
-
5
define_method :_generate_prefix do |options|
-
17
prefix_options = options.slice(*_route.segment_keys)
-
# we must actually delete prefix segment keys to avoid passing them to next url_for
-
31
_route.segment_keys.each { |k| options.delete(k) }
-
17
_routes.url_helpers.send("#{name}_path", prefix_options)
-
end
-
end
-
end
-
end
-
-
1
module HttpHelpers
-
# Define a route that only recognizes HTTP GET.
-
# For supported arguments, see <tt>Base#match</tt>.
-
#
-
# get 'bacon', to: 'food#bacon'
-
1
def get(*args, &block)
-
4483
map_method(:get, args, &block)
-
end
-
-
# Define a route that only recognizes HTTP POST.
-
# For supported arguments, see <tt>Base#match</tt>.
-
#
-
# post 'bacon', to: 'food#bacon'
-
1
def post(*args, &block)
-
669
map_method(:post, args, &block)
-
end
-
-
# Define a route that only recognizes HTTP PATCH.
-
# For supported arguments, see <tt>Base#match</tt>.
-
#
-
# patch 'bacon', to: 'food#bacon'
-
1
def patch(*args, &block)
-
600
map_method(:patch, args, &block)
-
end
-
-
# Define a route that only recognizes HTTP PUT.
-
# For supported arguments, see <tt>Base#match</tt>.
-
#
-
# put 'bacon', to: 'food#bacon'
-
1
def put(*args, &block)
-
608
map_method(:put, args, &block)
-
end
-
-
# Define a route that only recognizes HTTP DELETE.
-
# For supported arguments, see <tt>Base#match</tt>.
-
#
-
# delete 'broccoli', to: 'food#broccoli'
-
1
def delete(*args, &block)
-
595
map_method(:delete, args, &block)
-
end
-
-
1
private
-
1
def map_method(method, args, &block)
-
6955
options = args.extract_options!
-
6955
options[:via] = method
-
6955
options[:path] ||= args.first if args.first.is_a?(String)
-
6955
match(*args, options, &block)
-
6938
self
-
end
-
end
-
-
# You may wish to organize groups of controllers under a namespace.
-
# Most commonly, you might group a number of administrative controllers
-
# under an +admin+ namespace. You would place these controllers under
-
# the <tt>app/controllers/admin</tt> directory, and you can group them
-
# together in your router:
-
#
-
# namespace "admin" do
-
# resources :posts, :comments
-
# end
-
#
-
# This will create a number of routes for each of the posts and comments
-
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
-
#
-
# GET /admin/posts
-
# GET /admin/posts/new
-
# POST /admin/posts
-
# GET /admin/posts/1
-
# GET /admin/posts/1/edit
-
# PATCH/PUT /admin/posts/1
-
# DELETE /admin/posts/1
-
#
-
# If you want to route /posts (without the prefix /admin) to
-
# <tt>Admin::PostsController</tt>, you could use
-
#
-
# scope module: "admin" do
-
# resources :posts
-
# end
-
#
-
# or, for a single case
-
#
-
# resources :posts, module: "admin"
-
#
-
# If you want to route /admin/posts to +PostsController+
-
# (without the Admin:: module prefix), you could use
-
#
-
# scope "/admin" do
-
# resources :posts
-
# end
-
#
-
# or, for a single case
-
#
-
# resources :posts, path: "/admin/posts"
-
#
-
# In each of these cases, the named routes remain the same as if you did
-
# not use scope. In the last case, the following paths map to
-
# +PostsController+:
-
#
-
# GET /admin/posts
-
# GET /admin/posts/new
-
# POST /admin/posts
-
# GET /admin/posts/1
-
# GET /admin/posts/1/edit
-
# PATCH/PUT /admin/posts/1
-
# DELETE /admin/posts/1
-
1
module Scoping
-
# Scopes a set of routes to the given default options.
-
#
-
# Take the following route definition as an example:
-
#
-
# scope path: ":account_id", as: "account" do
-
# resources :projects
-
# end
-
#
-
# This generates helpers such as +account_projects_path+, just like +resources+ does.
-
# The difference here being that the routes generated are like /:account_id/projects,
-
# rather than /accounts/:account_id/projects.
-
#
-
# === Options
-
#
-
# Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
-
#
-
# === Examples
-
#
-
# # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
-
# scope module: "admin" do
-
# resources :posts
-
# end
-
#
-
# # prefix the posts resource's requests with '/admin'
-
# scope path: "/admin" do
-
# resources :posts
-
# end
-
#
-
# # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
-
# scope as: "sekret" do
-
# resources :posts
-
# end
-
1
def scope(*args)
-
3067
options = args.extract_options!
-
3067
options = options.dup
-
-
3067
options[:path] = args.first if args.first.is_a?(String)
-
3067
recover = {}
-
-
3067
options[:constraints] ||= {}
-
3067
unless options[:constraints].is_a?(Hash)
-
33
block, options[:constraints] = options[:constraints], {}
-
end
-
-
3067
if options[:constraints].is_a?(Hash)
-
3067
(options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(options[:constraints]))
-
end
-
-
3067
scope_options.each do |option|
-
36804
if value = options.delete(option)
-
10151
recover[option] = @scope[option]
-
10151
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
-
end
-
end
-
-
3067
recover[:block] = @scope[:blocks]
-
3067
@scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
-
-
3067
recover[:options] = @scope[:options]
-
3067
@scope[:options] = merge_options_scope(@scope[:options], options)
-
-
3067
yield
-
3062
self
-
ensure
-
3067
scope_options.each do |option|
-
36804
@scope[option] = recover[option] if recover.has_key?(option)
-
end
-
-
3067
@scope[:options] = recover[:options]
-
3067
@scope[:blocks] = recover[:block]
-
end
-
-
# Scopes routes to a specific controller
-
#
-
# controller "food" do
-
# match "bacon", action: "bacon"
-
# end
-
1
def controller(controller, options={})
-
4
options[:controller] = controller
-
8
scope(options) { yield }
-
end
-
-
# Scopes routes to a specific namespace. For example:
-
#
-
# namespace :admin do
-
# resources :posts
-
# end
-
#
-
# This generates the following routes:
-
#
-
# admin_posts GET /admin/posts(.:format) admin/posts#index
-
# admin_posts POST /admin/posts(.:format) admin/posts#create
-
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
-
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
-
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
-
# admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
-
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
-
#
-
# === Options
-
#
-
# The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+
-
# options all default to the name of the namespace.
-
#
-
# For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
-
# <tt>Resources#resources</tt>.
-
#
-
# === Examples
-
#
-
# # accessible through /sekret/posts rather than /admin/posts
-
# namespace :admin, path: "sekret" do
-
# resources :posts
-
# end
-
#
-
# # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
-
# namespace :admin, module: "sekret" do
-
# resources :posts
-
# end
-
#
-
# # generates +sekret_posts_path+ rather than +admin_posts_path+
-
# namespace :admin, as: "sekret" do
-
# resources :posts
-
# end
-
1
def namespace(path, options = {})
-
219
path = path.to_s
-
219
options = { :path => path, :as => path, :module => path,
-
:shallow_path => path, :shallow_prefix => path }.merge!(options)
-
438
scope(options) { yield }
-
end
-
-
# === Parameter Restriction
-
# Allows you to constrain the nested routes based on a set of rules.
-
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
-
#
-
# constraints(id: /\d+\.\d+/) do
-
# resources :posts
-
# end
-
#
-
# Now routes such as +/posts/1+ will no longer be valid, but +/posts/1.1+ will be.
-
# The +id+ parameter must match the constraint passed in for this example.
-
#
-
# You may use this to also restrict other parameters:
-
#
-
# resources :posts do
-
# constraints(post_id: /\d+\.\d+/) do
-
# resources :comments
-
# end
-
# end
-
#
-
# === Restricting based on IP
-
#
-
# Routes can also be constrained to an IP or a certain range of IP addresses:
-
#
-
# constraints(ip: /192\.168\.\d+\.\d+/) do
-
# resources :posts
-
# end
-
#
-
# Any user connecting from the 192.168.* range will be able to see this resource,
-
# where as any user connecting outside of this range will be told there is no such route.
-
#
-
# === Dynamic request matching
-
#
-
# Requests to routes can be constrained based on specific criteria:
-
#
-
# constraints(lambda { |req| req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
-
# resources :iphones
-
# end
-
#
-
# You are able to move this logic out into a class if it is too complex for routes.
-
# This class must have a +matches?+ method defined on it which either returns +true+
-
# if the user should be given access to that route, or +false+ if the user should not.
-
#
-
# class Iphone
-
# def self.matches?(request)
-
# request.env["HTTP_USER_AGENT"] =~ /iPhone/
-
# end
-
# end
-
#
-
# An expected place for this code would be +lib/constraints+.
-
#
-
# This class is then used like this:
-
#
-
# constraints(Iphone) do
-
# resources :iphones
-
# end
-
1
def constraints(constraints = {})
-
10
scope(:constraints => constraints) { yield }
-
end
-
-
# Allows you to set default parameters for a route, such as this:
-
# defaults id: 'home' do
-
# match 'scoped_pages/(:id)', to: 'pages#show'
-
# end
-
# Using this, the +:id+ parameter here will default to 'home'.
-
1
def defaults(defaults = {})
-
2
scope(:defaults => defaults) { yield }
-
end
-
-
1
private
-
1
def scope_options #:nodoc:
-
9266
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
-
end
-
-
1
def merge_path_scope(parent, child) #:nodoc:
-
2373
Mapper.normalize_path("#{parent}/#{child}")
-
end
-
-
1
def merge_shallow_path_scope(parent, child) #:nodoc:
-
220
Mapper.normalize_path("#{parent}/#{child}")
-
end
-
-
1
def merge_as_scope(parent, child) #:nodoc:
-
324
parent ? "#{parent}_#{child}" : child
-
end
-
-
1
def merge_shallow_prefix_scope(parent, child) #:nodoc:
-
219
parent ? "#{parent}_#{child}" : child
-
end
-
-
1
def merge_module_scope(parent, child) #:nodoc:
-
226
parent ? "#{parent}/#{child}" : child
-
end
-
-
1
def merge_controller_scope(parent, child) #:nodoc:
-
640
child
-
end
-
-
1
def merge_path_names_scope(parent, child) #:nodoc:
-
7
merge_options_scope(parent, child)
-
end
-
-
1
def merge_constraints_scope(parent, child) #:nodoc:
-
3067
merge_options_scope(parent, child)
-
end
-
-
1
def merge_defaults_scope(parent, child) #:nodoc:
-
3067
merge_options_scope(parent, child)
-
end
-
-
1
def merge_blocks_scope(parent, child) #:nodoc:
-
3067
merged = parent ? parent.dup : []
-
3067
merged << child if child
-
3067
merged
-
end
-
-
1
def merge_options_scope(parent, child) #:nodoc:
-
9208
(parent || {}).except(*override_keys(child)).merge(child)
-
end
-
-
1
def merge_shallow_scope(parent, child) #:nodoc:
-
8
child ? true : false
-
end
-
-
1
def override_keys(child) #:nodoc:
-
9208
child.key?(:only) || child.key?(:except) ? [:only, :except] : []
-
end
-
-
1
def defaults_from_constraints(constraints)
-
3067
url_keys = [:protocol, :subdomain, :domain, :host, :port]
-
3084
constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
-
end
-
end
-
-
# Resource routing allows you to quickly declare all of the common routes
-
# for a given resourceful controller. Instead of declaring separate routes
-
# for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+
-
# actions, a resourceful route declares them in a single line of code:
-
#
-
# resources :photos
-
#
-
# Sometimes, you have a resource that clients always look up without
-
# referencing an ID. A common example, /profile always shows the profile of
-
# the currently logged in user. In this case, you can use a singular resource
-
# to map /profile (rather than /profile/:id) to the show action.
-
#
-
# resource :profile
-
#
-
# It's common to have resources that are logically children of other
-
# resources:
-
#
-
# resources :magazines do
-
# resources :ads
-
# end
-
#
-
# You may wish to organize groups of controllers under a namespace. Most
-
# commonly, you might group a number of administrative controllers under
-
# an +admin+ namespace. You would place these controllers under the
-
# <tt>app/controllers/admin</tt> directory, and you can group them together
-
# in your router:
-
#
-
# namespace "admin" do
-
# resources :posts, :comments
-
# end
-
#
-
# By default the +:id+ parameter doesn't accept dots. If you need to
-
# use dots as part of the +:id+ parameter add a constraint which
-
# overrides this restriction, e.g:
-
#
-
# resources :articles, id: /[^\/]+/
-
#
-
# This allows any character other than a slash as part of your +:id+.
-
#
-
1
module Resources
-
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
-
# a path appended since they fit properly in their scope level.
-
1
VALID_ON_OPTIONS = [:new, :collection, :member]
-
1
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
-
1
CANONICAL_ACTIONS = %w(index create new show update destroy)
-
-
1
class Resource #:nodoc:
-
1
attr_reader :controller, :path, :options, :param
-
-
1
def initialize(entities, options = {})
-
634
@name = entities.to_s
-
634
@path = (options[:path] || @name).to_s
-
634
@controller = (options[:controller] || @name).to_s
-
634
@as = options[:as]
-
634
@param = (options[:param] || :id).to_sym
-
634
@options = options
-
end
-
-
1
def default_actions
-
3851
[:index, :create, :new, :show, :update, :destroy, :edit]
-
end
-
-
1
def actions
-
4368
if only = @options[:only]
-
241
Array(only).map(&:to_sym)
-
4127
elsif except = @options[:except]
-
109
default_actions - Array(except).map(&:to_sym)
-
else
-
4018
default_actions
-
end
-
end
-
-
1
def name
-
1263
@as || @name
-
end
-
-
1
def plural
-
8988
@plural ||= name.to_s
-
end
-
-
1
def singular
-
9170
@singular ||= name.to_s.singularize
-
end
-
-
1
alias :member_name :singular
-
-
# Checks for uncountable plurals, and appends "_index" if the plural
-
# and singular form are the same.
-
1
def collection_name
-
4494
singular == plural ? "#{plural}_index" : plural
-
end
-
-
1
def resource_scope
-
634
{ :controller => controller }
-
end
-
-
1
alias :collection_scope :path
-
-
1
def member_scope
-
689
"#{path}/:#{param}"
-
end
-
-
1
alias :shallow_scope :member_scope
-
-
1
def new_scope(new_path)
-
610
"#{path}/#{new_path}"
-
end
-
-
1
def nested_param
-
94
:"#{singular}_#{param}"
-
end
-
-
1
def nested_scope
-
88
"#{path}/:#{nested_param}"
-
end
-
-
end
-
-
1
class SingletonResource < Resource #:nodoc:
-
1
def initialize(entities, options)
-
57
super
-
57
@as = nil
-
57
@controller = (options[:controller] || plural).to_s
-
57
@as = options[:as]
-
end
-
-
1
def default_actions
-
276
[:show, :create, :update, :destroy, :new, :edit]
-
end
-
-
1
def plural
-
54
@plural ||= name.to_s.pluralize
-
end
-
-
1
def singular
-
705
@singular ||= name.to_s
-
end
-
-
1
alias :member_name :singular
-
1
alias :collection_name :singular
-
-
1
alias :member_scope :path
-
1
alias :nested_scope :path
-
end
-
-
1
def resources_path_names(options)
-
1
@scope[:path_names].merge!(options)
-
end
-
-
# Sometimes, you have a resource that clients always look up without
-
# referencing an ID. A common example, /profile always shows the
-
# profile of the currently logged in user. In this case, you can use
-
# a singular resource to map /profile (rather than /profile/:id) to
-
# the show action:
-
#
-
# resource :geocoder
-
#
-
# creates six different routes in your application, all mapping to
-
# the +GeoCoders+ controller (note that the controller is named after
-
# the plural):
-
#
-
# GET /geocoder/new
-
# POST /geocoder
-
# GET /geocoder
-
# GET /geocoder/edit
-
# PATCH/PUT /geocoder
-
# DELETE /geocoder
-
#
-
# === Options
-
# Takes same options as +resources+.
-
1
def resource(*resources, &block)
-
78
options = resources.extract_options!.dup
-
-
78
if apply_common_behavior_for(:resource, resources, options, &block)
-
21
return self
-
end
-
-
57
resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
-
57
yield if block_given?
-
-
57
concerns(options[:concerns]) if options[:concerns]
-
-
collection do
-
47
post :create
-
57
end if parent_resource.actions.include?(:create)
-
-
new do
-
45
get :new
-
57
end if parent_resource.actions.include?(:new)
-
-
57
member do
-
57
get :edit if parent_resource.actions.include?(:edit)
-
57
get :show if parent_resource.actions.include?(:show)
-
57
if parent_resource.actions.include?(:update)
-
47
patch :update
-
47
put :update
-
end
-
57
delete :destroy if parent_resource.actions.include?(:destroy)
-
end
-
end
-
-
57
self
-
end
-
-
# In Rails, a resourceful route provides a mapping between HTTP verbs
-
# and URLs and controller actions. By convention, each action also maps
-
# to particular CRUD operations in a database. A single entry in the
-
# routing file, such as
-
#
-
# resources :photos
-
#
-
# creates seven different routes in your application, all mapping to
-
# the +Photos+ controller:
-
#
-
# GET /photos
-
# GET /photos/new
-
# POST /photos
-
# GET /photos/:id
-
# GET /photos/:id/edit
-
# PATCH/PUT /photos/:id
-
# DELETE /photos/:id
-
#
-
# Resources can also be nested infinitely by using this block syntax:
-
#
-
# resources :photos do
-
# resources :comments
-
# end
-
#
-
# This generates the following comments routes:
-
#
-
# GET /photos/:photo_id/comments
-
# GET /photos/:photo_id/comments/new
-
# POST /photos/:photo_id/comments
-
# GET /photos/:photo_id/comments/:id
-
# GET /photos/:photo_id/comments/:id/edit
-
# PATCH/PUT /photos/:photo_id/comments/:id
-
# DELETE /photos/:photo_id/comments/:id
-
#
-
# === Options
-
# Takes same options as <tt>Base#match</tt> as well as:
-
#
-
# [:path_names]
-
# Allows you to change the segment component of the +edit+ and +new+ actions.
-
# Actions not specified are not changed.
-
#
-
# resources :posts, path_names: { new: "brand_new" }
-
#
-
# The above example will now change /posts/new to /posts/brand_new
-
#
-
# [:path]
-
# Allows you to change the path prefix for the resource.
-
#
-
# resources :posts, path: 'postings'
-
#
-
# The resource and all segments will now route to /postings instead of /posts
-
#
-
# [:only]
-
# Only generate routes for the given actions.
-
#
-
# resources :cows, only: :show
-
# resources :cows, only: [:show, :index]
-
#
-
# [:except]
-
# Generate all routes except for the given actions.
-
#
-
# resources :cows, except: :show
-
# resources :cows, except: [:show, :index]
-
#
-
# [:shallow]
-
# Generates shallow routes for nested resource(s). When placed on a parent resource,
-
# generates shallow routes for all nested resources.
-
#
-
# resources :posts, shallow: true do
-
# resources :comments
-
# end
-
#
-
# Is the same as:
-
#
-
# resources :posts do
-
# resources :comments, except: [:show, :edit, :update, :destroy]
-
# end
-
# resources :comments, only: [:show, :edit, :update, :destroy]
-
#
-
# This allows URLs for resources that otherwise would be deeply nested such
-
# as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
-
# to be shortened to just <tt>/comments/1234</tt>.
-
#
-
# [:shallow_path]
-
# Prefixes nested shallow routes with the specified path.
-
#
-
# scope shallow_path: "sekret" do
-
# resources :posts do
-
# resources :comments, shallow: true
-
# end
-
# end
-
#
-
# The +comments+ resource here will have the following routes generated for it:
-
#
-
# post_comments GET /posts/:post_id/comments(.:format)
-
# post_comments POST /posts/:post_id/comments(.:format)
-
# new_post_comment GET /posts/:post_id/comments/new(.:format)
-
# edit_comment GET /sekret/comments/:id/edit(.:format)
-
# comment GET /sekret/comments/:id(.:format)
-
# comment PATCH/PUT /sekret/comments/:id(.:format)
-
# comment DELETE /sekret/comments/:id(.:format)
-
#
-
# [:shallow_prefix]
-
# Prefixes nested shallow route names with specified prefix.
-
#
-
# scope shallow_prefix: "sekret" do
-
# resources :posts do
-
# resources :comments, shallow: true
-
# end
-
# end
-
#
-
# The +comments+ resource here will have the following routes generated for it:
-
#
-
# post_comments GET /posts/:post_id/comments(.:format)
-
# post_comments POST /posts/:post_id/comments(.:format)
-
# new_post_comment GET /posts/:post_id/comments/new(.:format)
-
# edit_sekret_comment GET /comments/:id/edit(.:format)
-
# sekret_comment GET /comments/:id(.:format)
-
# sekret_comment PATCH/PUT /comments/:id(.:format)
-
# sekret_comment DELETE /comments/:id(.:format)
-
#
-
# [:format]
-
# Allows you to specify the default value for optional +format+
-
# segment or disable it by supplying +false+.
-
#
-
# === Examples
-
#
-
# # routes call <tt>Admin::PostsController</tt>
-
# resources :posts, module: "admin"
-
#
-
# # resource actions are at /admin/posts.
-
# resources :posts, path: "admin/posts"
-
1
def resources(*resources, &block)
-
773
options = resources.extract_options!.dup
-
-
773
if apply_common_behavior_for(:resources, resources, options, &block)
-
196
return self
-
end
-
-
577
resource_scope(:resources, Resource.new(resources.pop, options)) do
-
577
yield if block_given?
-
-
577
concerns(options[:concerns]) if options[:concerns]
-
-
576
collection do
-
576
get :index if parent_resource.actions.include?(:index)
-
575
post :create if parent_resource.actions.include?(:create)
-
end
-
-
new do
-
549
get :new
-
575
end if parent_resource.actions.include?(:new)
-
-
575
member do
-
575
get :edit if parent_resource.actions.include?(:edit)
-
575
get :show if parent_resource.actions.include?(:show)
-
575
if parent_resource.actions.include?(:update)
-
546
patch :update
-
546
put :update
-
end
-
575
delete :destroy if parent_resource.actions.include?(:destroy)
-
end
-
end
-
-
575
self
-
end
-
-
# To add a route to the collection:
-
#
-
# resources :photos do
-
# collection do
-
# get 'search'
-
# end
-
# end
-
#
-
# This will enable Rails to recognize paths such as <tt>/photos/search</tt>
-
# with GET, and route to the search action of +PhotosController+. It will also
-
# create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>
-
# route helpers.
-
1
def collection
-
658
unless resource_scope?
-
raise ArgumentError, "can't use collection outside resource(s) scope"
-
end
-
-
658
with_scope_level(:collection) do
-
658
scope(parent_resource.collection_scope) do
-
658
yield
-
end
-
end
-
end
-
-
# To add a member route, add a member block into the resource block:
-
#
-
# resources :photos do
-
# member do
-
# get 'preview'
-
# end
-
# end
-
#
-
# This will recognize <tt>/photos/1/preview</tt> with GET, and route to the
-
# preview action of +PhotosController+. It will also create the
-
# <tt>preview_photo_url</tt> and <tt>preview_photo_path</tt> helpers.
-
1
def member
-
682
unless resource_scope?
-
raise ArgumentError, "can't use member outside resource(s) scope"
-
end
-
-
682
with_scope_level(:member) do
-
682
scope(parent_resource.member_scope) do
-
682
yield
-
end
-
end
-
end
-
-
1
def new
-
610
unless resource_scope?
-
raise ArgumentError, "can't use new outside resource(s) scope"
-
end
-
-
610
with_scope_level(:new) do
-
610
scope(parent_resource.new_scope(action_path(:new))) do
-
610
yield
-
end
-
end
-
end
-
-
1
def nested
-
97
unless resource_scope?
-
raise ArgumentError, "can't use nested outside resource(s) scope"
-
end
-
-
97
with_scope_level(:nested) do
-
97
if shallow?
-
10
with_exclusive_scope do
-
10
if @scope[:shallow_path].blank?
-
12
scope(parent_resource.nested_scope, nested_options) { yield }
-
else
-
4
scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
-
8
scope(parent_resource.nested_scope, nested_options) { yield }
-
end
-
end
-
end
-
else
-
174
scope(parent_resource.nested_scope, nested_options) { yield }
-
end
-
end
-
end
-
-
# See ActionDispatch::Routing::Mapper::Scoping#namespace
-
1
def namespace(path, options = {})
-
219
if resource_scope?
-
6
nested { super }
-
else
-
216
super
-
end
-
end
-
-
1
def shallow
-
2
scope(:shallow => true, :shallow_path => @scope[:path]) do
-
2
yield
-
end
-
end
-
-
1
def shallow?
-
11371
parent_resource.instance_of?(Resource) && @scope[:shallow]
-
end
-
-
# match 'path' => 'controller#action'
-
# match 'path', to: 'controller#action'
-
# match 'path', 'otherpath', on: :member, via: :get
-
1
def match(path, *rest)
-
8239
if rest.empty? && Hash === path
-
2130
options = path
-
4260
path, to = options.find { |name, value| name.is_a?(String) }
-
2130
options[:to] = to
-
2130
options.delete(path)
-
2130
paths = [path]
-
else
-
6109
options = rest.pop || {}
-
6109
paths = [path] + rest
-
end
-
-
8239
options[:anchor] = true unless options.key?(:anchor)
-
-
8239
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
-
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
-
end
-
-
16480
paths.each { |_path| decomposed_match(_path, options.dup) }
-
8222
self
-
end
-
-
1
def decomposed_match(path, options) # :nodoc:
-
8319
if on = options.delete(:on)
-
144
send(on) { decomposed_match(path, options) }
-
else
-
8247
case @scope[:scope_level]
-
when :resources
-
4
nested { decomposed_match(path, options) }
-
when :resource
-
8
member { decomposed_match(path, options) }
-
else
-
8241
add_route(path, options)
-
end
-
end
-
end
-
-
1
def add_route(action, options) # :nodoc:
-
8241
path = path_for_action(action, options.delete(:path))
-
-
8241
if action.to_s =~ /^[\w\/]+$/
-
6455
options[:action] ||= action unless action.to_s.include?("/")
-
else
-
1786
action = nil
-
end
-
-
8241
if !options.fetch(:as, true)
-
7
options.delete(:as)
-
else
-
8234
options[:as] = name_for_action(options[:as], action)
-
end
-
-
8241
mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
-
8229
app, conditions, requirements, defaults, as, anchor = mapping.to_route
-
8229
@set.add_route(app, conditions, requirements, defaults, as, anchor)
-
end
-
-
1
def root(options={})
-
201
if @scope[:scope_level] == :resources
-
1
with_scope_level(:root) do
-
1
scope(parent_resource.path) do
-
1
super(options)
-
end
-
end
-
else
-
200
super(options)
-
end
-
end
-
-
1
protected
-
-
1
def parent_resource #:nodoc:
-
38685
@scope[:scope_level_resource]
-
end
-
-
1
def apply_common_behavior_for(method, resources, options, &block) #:nodoc:
-
851
if resources.length > 1
-
292
resources.each { |r| send(method, r, options, &block) }
-
97
return true
-
end
-
-
754
if resource_scope?
-
182
nested { send(method, resources.pop, options, &block) }
-
91
return true
-
end
-
-
663
options.keys.each do |k|
-
154
(options[:constraints] ||= {})[k] = options.delete(k) if options[k].is_a?(Regexp)
-
end
-
-
663
scope_options = options.slice!(*RESOURCE_OPTIONS)
-
663
unless scope_options.empty?
-
29
scope(scope_options) do
-
29
send(method, resources.pop, options, &block)
-
end
-
29
return true
-
end
-
-
634
unless action_options?(options)
-
599
options.merge!(scope_action_options) if scope_action_options?
-
end
-
-
634
false
-
end
-
-
1
def action_options?(options) #:nodoc:
-
634
options[:only] || options[:except]
-
end
-
-
1
def scope_action_options? #:nodoc:
-
599
@scope[:options] && (@scope[:options][:only] || @scope[:options][:except])
-
end
-
-
1
def scope_action_options #:nodoc:
-
17
@scope[:options].slice(:only, :except)
-
end
-
-
1
def resource_scope? #:nodoc:
-
3020
[:resource, :resources].include? @scope[:scope_level]
-
end
-
-
1
def resource_method_scope? #:nodoc:
-
12023
[:collection, :member, :new].include? @scope[:scope_level]
-
end
-
-
1
def with_exclusive_scope
-
10
begin
-
10
old_name_prefix, old_path = @scope[:as], @scope[:path]
-
10
@scope[:as], @scope[:path] = nil, nil
-
-
10
with_scope_level(:exclusive) do
-
10
yield
-
end
-
ensure
-
10
@scope[:as], @scope[:path] = old_name_prefix, old_path
-
end
-
end
-
-
1
def with_scope_level(kind, resource = parent_resource)
-
2692
old, @scope[:scope_level] = @scope[:scope_level], kind
-
2692
old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
-
2692
yield
-
ensure
-
2692
@scope[:scope_level] = old
-
2692
@scope[:scope_level_resource] = old_resource
-
end
-
-
1
def resource_scope(kind, resource) #:nodoc:
-
634
with_scope_level(kind, resource) do
-
634
scope(parent_resource.resource_scope) do
-
634
yield
-
end
-
end
-
end
-
-
1
def nested_options #:nodoc:
-
97
options = { :as => parent_resource.member_name }
-
options[:constraints] = {
-
parent_resource.nested_param => param_constraint
-
97
} if param_constraint?
-
-
97
options
-
end
-
-
1
def param_constraint? #:nodoc:
-
97
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
-
end
-
-
1
def param_constraint #:nodoc:
-
6
@scope[:constraints][parent_resource.param]
-
end
-
-
1
def canonical_action?(action, flag) #:nodoc:
-
15847
flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
-
end
-
-
1
def shallow_scoping? #:nodoc:
-
11274
shallow? && @scope[:scope_level] == :member
-
end
-
-
1
def path_for_action(action, path) #:nodoc:
-
8241
prefix = shallow_scoping? ?
-
"#{@scope[:shallow_path]}/#{parent_resource.shallow_scope}" : @scope[:path]
-
-
8241
if canonical_action?(action, path.blank?)
-
4135
prefix.to_s
-
else
-
4106
"#{prefix}/#{action_path(action, path)}"
-
end
-
end
-
-
1
def action_path(name, path = nil) #:nodoc:
-
4716
name = name.to_sym if name.is_a?(String)
-
4716
path || @scope[:path_names][name] || name.to_s
-
end
-
-
1
def prefix_name_for_action(as, action) #:nodoc:
-
8234
if as
-
628
as.to_s
-
7606
elsif !canonical_action?(action, @scope[:scope_level])
-
3471
action.to_s
-
end
-
end
-
-
1
def name_for_action(as, action) #:nodoc:
-
8234
prefix = prefix_name_for_action(as, action)
-
8234
prefix = Mapper.normalize_name(prefix) if prefix
-
8234
name_prefix = @scope[:as]
-
-
8234
if parent_resource
-
4844
return nil unless as || action
-
-
4842
collection_name = parent_resource.collection_name
-
4842
member_name = parent_resource.member_name
-
end
-
-
8232
name = case @scope[:scope_level]
-
when :nested
-
3
[name_prefix, prefix]
-
when :collection
-
1195
[prefix, name_prefix, collection_name]
-
when :new
-
610
[prefix, :new, name_prefix, member_name]
-
when :member
-
3033
[prefix, shallow_scoping? ? @scope[:shallow_prefix] : name_prefix, member_name]
-
when :root
-
1
[name_prefix, collection_name, prefix]
-
else
-
3390
[name_prefix, member_name, prefix]
-
end
-
-
8232
if candidate = name.select(&:present?).join("_").presence
-
# If a name was not explicitly given, we check if it is valid
-
# and return nil in case it isn't. Otherwise, we pass the invalid name
-
# forward so the underlying router engine treats it and raises an exception.
-
5855
if as.nil?
-
307996
candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i
-
else
-
628
candidate
-
end
-
end
-
end
-
end
-
-
# Routing Concerns allow you to declare common routes that can be reused
-
# inside others resources and routes.
-
#
-
# concern :commentable do
-
# resources :comments
-
# end
-
#
-
# concern :image_attachable do
-
# resources :images, only: :index
-
# end
-
#
-
# These concerns are used in Resources routing:
-
#
-
# resources :messages, concerns: [:commentable, :image_attachable]
-
#
-
# or in a scope or namespace:
-
#
-
# namespace :posts do
-
# concerns :commentable
-
# end
-
1
module Concerns
-
# Define a routing concern using a name.
-
#
-
# Concerns may be defined inline, using a block, or handled by
-
# another object, by passing that object as the second parameter.
-
#
-
# The concern object, if supplied, should respond to <tt>call</tt>,
-
# which will receive two parameters:
-
#
-
# * The current mapper
-
# * A hash of options which the concern object may use
-
#
-
# Options may also be used by concerns defined in a block by accepting
-
# a block parameter. So, using a block, you might do something as
-
# simple as limit the actions available on certain resources, passing
-
# standard resource options through the concern:
-
#
-
# concern :commentable do |options|
-
# resources :comments, options
-
# end
-
#
-
# resources :posts, concerns: :commentable
-
# resources :archived_posts do
-
# # Don't allow comments on archived posts
-
# concerns :commentable, only: [:index, :show]
-
# end
-
#
-
# Or, using a callable object, you might implement something more
-
# specific to your application, which would be out of place in your
-
# routes file.
-
#
-
# # purchasable.rb
-
# class Purchasable
-
# def initialize(defaults = {})
-
# @defaults = defaults
-
# end
-
#
-
# def call(mapper, options = {})
-
# options = @defaults.merge(options)
-
# mapper.resources :purchases
-
# mapper.resources :receipts
-
# mapper.resources :returns if options[:returnable]
-
# end
-
# end
-
#
-
# # routes.rb
-
# concern :purchasable, Purchasable.new(returnable: true)
-
#
-
# resources :toys, concerns: :purchasable
-
# resources :electronics, concerns: :purchasable
-
# resources :pets do
-
# concerns :purchasable, returnable: false
-
# end
-
#
-
# Any routing helpers can be used inside a concern. If using a
-
# callable, they're accessible from the Mapper that's passed to
-
# <tt>call</tt>.
-
1
def concern(name, callable = nil, &block)
-
11
callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
-
4
@concerns[name] = callable
-
end
-
-
# Use the named concerns
-
#
-
# resources :posts do
-
# concerns :commentable
-
# end
-
#
-
# concerns also work in any routes helper that you want to use:
-
#
-
# namespace :posts do
-
# concerns :commentable
-
# end
-
1
def concerns(*args)
-
8
options = args.extract_options!
-
8
args.flatten.each do |name|
-
10
if concern = @concerns[name]
-
9
concern.call(self, options)
-
else
-
1
raise ArgumentError, "No concern named #{name} was found!"
-
end
-
end
-
end
-
end
-
-
1
def initialize(set) #:nodoc:
-
757
@set = set
-
757
@scope = { :path_names => @set.resources_path_names }
-
757
@concerns = {}
-
end
-
-
1
include Base
-
1
include HttpHelpers
-
1
include Redirection
-
1
include Scoping
-
1
include Concerns
-
1
include Resources
-
end
-
end
-
end
-
1
require 'action_controller/model_naming'
-
-
1
module ActionDispatch
-
1
module Routing
-
# Polymorphic URL helpers are methods for smart resolution to a named route call when
-
# given an Active Record model instance. They are to be used in combination with
-
# ActionController::Resources.
-
#
-
# These methods are useful when you want to generate correct URL or path to a RESTful
-
# resource without having to know the exact type of the record in question.
-
#
-
# Nested resources and/or namespaces are also supported, as illustrated in the example:
-
#
-
# polymorphic_url([:admin, @article, @comment])
-
#
-
# results in:
-
#
-
# admin_article_comment_url(@article, @comment)
-
#
-
# == Usage within the framework
-
#
-
# Polymorphic URL helpers are used in a number of places throughout the \Rails framework:
-
#
-
# * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
-
# <tt>url_for(@article)</tt>;
-
# * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
-
# <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
-
# action;
-
# * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
-
# <tt>redirect_to(post)</tt> in your controllers;
-
# * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
-
# for feed entries.
-
#
-
# == Prefixed polymorphic helpers
-
#
-
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
-
# number of prefixed helpers are available as a shorthand to <tt>action: "..."</tt>
-
# in options. Those are:
-
#
-
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
-
# * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
-
#
-
# Example usage:
-
#
-
# edit_polymorphic_path(@post) # => "/posts/1/edit"
-
# polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf"
-
#
-
# == Usage with mounted engines
-
#
-
# If you are using a mounted engine and you need to use a polymorphic_url
-
# pointing at the engine's routes, pass in the engine's route proxy as the first
-
# argument to the method. For example:
-
#
-
# polymorphic_url([blog, @post]) # calls blog.post_path(@post)
-
# form_for([blog, @post]) # => "/blog/posts/1"
-
#
-
1
module PolymorphicRoutes
-
1
include ActionController::ModelNaming
-
-
# Constructs a call to a named RESTful route for the given record and returns the
-
# resulting URL string. For example:
-
#
-
# # calls post_url(post)
-
# polymorphic_url(post) # => "http://example.com/posts/1"
-
# polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
-
# polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
-
# polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
-
# polymorphic_url(Comment) # => "http://example.com/comments"
-
#
-
# ==== Options
-
#
-
# * <tt>:action</tt> - Specifies the action prefix for the named route:
-
# <tt>:new</tt> or <tt>:edit</tt>. Default is no prefix.
-
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
-
# Default is <tt>:url</tt>.
-
#
-
# ==== Examples
-
#
-
# # an Article record
-
# polymorphic_url(record) # same as article_url(record)
-
#
-
# # a Comment record
-
# polymorphic_url(record) # same as comment_url(record)
-
#
-
# # it recognizes new records and maps to the collection
-
# record = Comment.new
-
# polymorphic_url(record) # same as comments_url()
-
#
-
# # the class of a record will also map to the collection
-
# polymorphic_url(Comment) # same as comments_url()
-
#
-
1
def polymorphic_url(record_or_hash_or_array, options = {})
-
140
if record_or_hash_or_array.kind_of?(Array)
-
16
record_or_hash_or_array = record_or_hash_or_array.compact
-
16
if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
-
proxy = record_or_hash_or_array.shift
-
end
-
16
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
-
end
-
-
140
record = extract_record(record_or_hash_or_array)
-
140
record = convert_to_model(record)
-
-
140
args = Array === record_or_hash_or_array ?
-
record_or_hash_or_array.dup :
-
[ record_or_hash_or_array ]
-
-
140
inflection = if options[:action] && options[:action].to_s == "new"
-
args.pop
-
:singular
-
elsif (record.respond_to?(:persisted?) && !record.persisted?)
-
43
args.pop
-
43
:plural
-
elsif record.is_a?(Class)
-
args.pop
-
:plural
-
else
-
97
:singular
-
end
-
-
248
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
-
140
named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
-
-
139
url_options = options.except(:action, :routing_type)
-
139
unless url_options.empty?
-
83
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
-
end
-
-
327
args.collect! { |a| convert_to_model(a) }
-
-
139
(proxy || self).send(named_route, *args)
-
end
-
-
# Returns the path component of a URL for the given record. It uses
-
# <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>.
-
1
def polymorphic_path(record_or_hash_or_array, options = {})
-
96
polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
-
end
-
-
1
%w(edit new).each do |action|
-
2
module_eval <<-EOT, __FILE__, __LINE__ + 1
-
def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
-
polymorphic_url( # polymorphic_url(
-
record_or_hash, # record_or_hash,
-
options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
-
end # end
-
#
-
def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
-
polymorphic_url( # polymorphic_url(
-
record_or_hash, # record_or_hash,
-
options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
-
end # end
-
EOT
-
end
-
-
1
private
-
1
def action_prefix(options)
-
139
options[:action] ? "#{options[:action]}_" : ''
-
end
-
-
1
def routing_type(options)
-
139
options[:routing_type] || :url
-
end
-
-
1
def build_named_route_call(records, inflection, options = {})
-
140
if records.is_a?(Array)
-
9
record = records.pop
-
9
route = records.map do |parent|
-
11
if parent.is_a?(Symbol) || parent.is_a?(String)
-
2
parent
-
else
-
9
model_name_from_record_or_class(parent).singular_route_key
-
end
-
end
-
else
-
131
record = extract_record(records)
-
131
route = []
-
end
-
-
140
if record.is_a?(Symbol) || record.is_a?(String)
-
route << record
-
elsif record
-
139
if inflection == :singular
-
96
route << model_name_from_record_or_class(record).singular_route_key
-
else
-
43
route << model_name_from_record_or_class(record).route_key
-
end
-
else
-
1
raise ArgumentError, "Nil location provided. Can't build URI."
-
end
-
-
139
route << routing_type(options)
-
-
139
action_prefix(options) + route.join("_")
-
end
-
-
1
def extract_record(record_or_hash_or_array)
-
271
case record_or_hash_or_array
-
9
when Array; record_or_hash_or_array.last
-
2
when Hash; record_or_hash_or_array[:id]
-
260
else record_or_hash_or_array
-
end
-
end
-
end
-
end
-
end
-
-
1
require 'action_dispatch/http/request'
-
1
require 'active_support/core_ext/uri'
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'rack/utils'
-
1
require 'action_controller/metal/exceptions'
-
-
1
module ActionDispatch
-
1
module Routing
-
1
class Redirect # :nodoc:
-
1
attr_reader :status, :block
-
-
1
def initialize(status, block)
-
22
@status = status
-
22
@block = block
-
end
-
-
1
def call(env)
-
21
req = Request.new(env)
-
-
# If any of the path parameters has a invalid encoding then
-
# raise since it's likely to trigger errors further on.
-
21
req.symbolized_path_parameters.each do |key, value|
-
18
unless value.valid_encoding?
-
1
raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
-
end
-
end
-
-
20
uri = URI.parse(path(req.symbolized_path_parameters, req))
-
20
uri.scheme ||= req.scheme
-
20
uri.host ||= req.host
-
20
uri.port ||= req.port unless req.standard_port?
-
-
20
body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
-
-
20
headers = {
-
'Location' => uri.to_s,
-
'Content-Type' => 'text/html',
-
'Content-Length' => body.length.to_s
-
}
-
-
20
[ status, headers, [body] ]
-
end
-
-
1
def path(params, request)
-
5
block.call params, request
-
end
-
-
1
def inspect
-
2
"redirect(#{status})"
-
end
-
end
-
-
1
class PathRedirect < Redirect
-
1
def path(params, request)
-
8
(params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params))
-
end
-
-
1
def inspect
-
2
"redirect(#{status}, #{block})"
-
end
-
-
1
private
-
1
def escape(params)
-
4
Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
-
end
-
end
-
-
1
class OptionRedirect < Redirect # :nodoc:
-
1
alias :options :block
-
-
1
def path(params, request)
-
7
url_options = {
-
:protocol => request.protocol,
-
:host => request.host,
-
:port => request.optional_port,
-
:path => request.path,
-
:params => request.query_parameters
-
}.merge options
-
-
7
if !params.empty? && url_options[:path].match(/%\{\w*\}/)
-
3
url_options[:path] = (url_options[:path] % escape_path(params))
-
end
-
-
7
ActionDispatch::Http::URL.url_for url_options
-
end
-
-
1
def inspect
-
4
"redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
-
end
-
-
1
private
-
1
def escape_path(params)
-
7
Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
-
end
-
end
-
-
1
module Redirection
-
-
# Redirect any path to another path:
-
#
-
# get "/stories" => redirect("/posts")
-
#
-
# You can also use interpolation in the supplied redirect argument:
-
#
-
# get 'docs/:article', to: redirect('/wiki/%{article}')
-
#
-
# Alternatively you can use one of the other syntaxes:
-
#
-
# The block version of redirect allows for the easy encapsulation of any logic associated with
-
# the redirect in question. Either the params and request are supplied as arguments, or just
-
# params, depending of how many arguments your block accepts. A string is required as a
-
# return value.
-
#
-
# get 'jokes/:number', to: redirect { |params, request|
-
# path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
-
# "http://#{request.host_with_port}/#{path}"
-
# }
-
#
-
# Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass
-
# the block to +get+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
-
#
-
# The options version of redirect allows you to supply only the parts of the url which need
-
# to change, it also supports interpolation of the path similar to the first example.
-
#
-
# get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}')
-
# get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}')
-
#
-
# Finally, an object which responds to call can be supplied to redirect, allowing you to reuse
-
# common redirect routes. The call method must accept two arguments, params and request, and return
-
# a string.
-
#
-
# get 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
-
#
-
1
def redirect(*args, &block)
-
23
options = args.extract_options!
-
23
status = options.delete(:status) || 301
-
23
path = args.shift
-
-
23
return OptionRedirect.new(status, options) if options.any?
-
15
return PathRedirect.new(status, path) if String === path
-
-
6
block = path if path.respond_to? :call
-
6
raise ArgumentError, "redirection argument not supported" unless block
-
5
Redirect.new status, block
-
end
-
end
-
end
-
end
-
1
require 'journey'
-
1
require 'forwardable'
-
1
require 'active_support/core_ext/object/to_query'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'action_controller/metal/exceptions'
-
-
1
module ActionDispatch
-
1
module Routing
-
1
class RouteSet #:nodoc:
-
# Since the router holds references to many parts of the system
-
# like engines, controllers and the application itself, inspecting
-
# the route set can actually be really slow, therefore we default
-
# alias inspect to to_s.
-
1
alias inspect to_s
-
-
1
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
-
-
1
class Dispatcher #:nodoc:
-
1
def initialize(options={})
-
7122
@defaults = options[:defaults]
-
7122
@glob_param = options.delete(:glob)
-
7122
@controllers = {}
-
end
-
-
1
def call(env)
-
454
params = env[PARAMETERS_KEY]
-
-
# If any of the path parameters has a invalid encoding then
-
# raise since it's likely to trigger errors further on.
-
454
params.each do |key, value|
-
1139
unless value.valid_encoding?
-
3
raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
-
end
-
end
-
-
451
prepare_params!(params)
-
-
# Just raise undefined constant errors if a controller was specified as default.
-
451
unless controller = controller(params, @defaults.key?(:controller))
-
return [404, {'X-Cascade' => 'pass'}, []]
-
end
-
-
451
dispatch(controller, params[:action], env)
-
end
-
-
1
def prepare_params!(params)
-
2256
normalize_controller!(params)
-
2256
merge_default_action!(params)
-
2256
split_glob_param!(params) if @glob_param
-
end
-
-
# If this is a default_controller (i.e. a controller specified by the user)
-
# we should raise an error in case it's not found, because it usually means
-
# a user error. However, if the controller was retrieved through a dynamic
-
# segment, as in :controller(/:action), we should simply return nil and
-
# delegate the control back to Rack cascade. Besides, if this is not a default
-
# controller, it means we should respect the @scope[:module] parameter.
-
1
def controller(params, default_controller=true)
-
2264
if params && params.key?(:controller)
-
2264
controller_param = params[:controller]
-
2264
controller_reference(controller_param)
-
end
-
rescue NameError => e
-
8
raise ActionController::RoutingError, e.message, e.backtrace if default_controller
-
end
-
-
1
private
-
-
1
def controller_reference(controller_param)
-
1985
controller_name = "#{controller_param.camelize}Controller"
-
-
1985
unless controller = @controllers[controller_param]
-
751
controller = @controllers[controller_param] =
-
ActiveSupport::Dependencies.reference(controller_name)
-
end
-
1985
controller.get(controller_name)
-
end
-
-
1
def dispatch(controller, action, env)
-
172
controller.action(action).call(env)
-
end
-
-
1
def normalize_controller!(params)
-
2256
params[:controller] = params[:controller].underscore if params.key?(:controller)
-
end
-
-
1
def merge_default_action!(params)
-
2256
params[:action] ||= 'index'
-
end
-
-
1
def split_glob_param!(params)
-
params[@glob_param] = params[@glob_param].split('/').map { |v| URI.parser.unescape(v) }
-
end
-
end
-
-
# A NamedRouteCollection instance is a collection of named routes, and also
-
# maintains an anonymous module that can be used to install helpers for the
-
# named routes.
-
1
class NamedRouteCollection #:nodoc:
-
1
include Enumerable
-
1
attr_reader :routes, :helpers, :module
-
-
1
def initialize
-
741
@routes = {}
-
741
@helpers = []
-
741
@module = Module.new do
-
741
protected
-
-
741
def handle_positional_args(args, options, segment_keys)
-
967
inner_options = args.extract_options!
-
967
result = options.dup
-
-
967
if args.size > 0
-
124
keys = segment_keys
-
124
if args.size < keys.size - 1 # take format into account
-
4
keys -= self.url_options.keys if self.respond_to?(:url_options)
-
4
keys -= options.keys
-
end
-
124
result.merge!(Hash[keys.zip(args)])
-
end
-
-
967
result.merge!(inner_options)
-
end
-
end
-
end
-
-
1
def helper_names
-
381
self.module.instance_methods.map(&:to_s)
-
end
-
-
1
def clear!
-
746
@routes.clear
-
746
@helpers.clear
-
end
-
-
1
def add(name, route)
-
3269
routes[name.to_sym] = route
-
3269
define_named_route_methods(name, route)
-
end
-
-
1
def get(name)
-
4392
routes[name.to_sym]
-
end
-
-
1
alias []= add
-
1
alias [] get
-
1
alias clear clear!
-
-
1
def each
-
routes.each { |name, route| yield name, route }
-
self
-
end
-
-
1
def names
-
1
routes.keys
-
end
-
-
1
def length
-
routes.length
-
end
-
-
1
private
-
-
1
def define_named_route_methods(name, route)
-
3269
define_url_helper route, :"#{name}_path",
-
route.defaults.merge(:use_route => name, :only_path => true)
-
3269
define_url_helper route, :"#{name}_url",
-
route.defaults.merge(:use_route => name, :only_path => false)
-
end
-
-
# Create a url helper allowing ordered parameters to be associated
-
# with corresponding dynamic segments, so you can do:
-
#
-
# foo_url(bar, baz, bang)
-
#
-
# Instead of:
-
#
-
# foo_url(bar: bar, baz: baz, bang: bang)
-
#
-
# Also allow options hash, so you can do:
-
#
-
# foo_url(bar, baz, bang, sort_by: 'baz')
-
#
-
1
def define_url_helper(route, name, options)
-
6538
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
-
remove_possible_method :#{name}
-
def #{name}(*args)
-
if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
-
options = #{options.inspect}
-
options.merge!(url_options) if respond_to?(:url_options)
-
options[:path] = "#{optimized_helper(route)}"
-
ActionDispatch::Http::URL.url_for(options)
-
else
-
url_for(handle_positional_args(args, #{options.inspect}, #{route.segment_keys.inspect}))
-
end
-
end
-
END_EVAL
-
-
6538
helpers << name
-
end
-
-
# Clause check about when we need to generate an optimized helper.
-
1
def optimize_helper?(route) #:nodoc:
-
6538
route.requirements.except(:controller, :action).empty?
-
end
-
-
# Generates the interpolation to be used in the optimized helper.
-
1
def optimized_helper(route)
-
6538
string_route = route.ast.to_s
-
-
6538
while string_route.gsub!(/\([^\)]*\)/, "")
-
6312
true
-
end
-
-
6538
route.required_parts.each_with_index do |part, i|
-
# Replace each route parameter
-
# e.g. :id for regular parameter or *path for globbing
-
# with ruby string interpolation code
-
3624
string_route.gsub!(/(\*|:)#{part}/, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}")
-
end
-
-
6538
string_route
-
end
-
end
-
-
1
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
-
1
attr_accessor :disable_clear_and_finalize, :resources_path_names
-
1
attr_accessor :default_url_options, :request_class
-
-
1
alias :routes :set
-
-
1
def self.default_resources_path_names
-
741
{ :new => 'new', :edit => 'edit' }
-
end
-
-
1
def initialize(request_class = ActionDispatch::Request)
-
741
self.named_routes = NamedRouteCollection.new
-
741
self.resources_path_names = self.class.default_resources_path_names.dup
-
741
self.default_url_options = {}
-
741
self.request_class = request_class
-
-
741
@append = []
-
741
@prepend = []
-
741
@disable_clear_and_finalize = false
-
741
@finalized = false
-
-
741
@set = Journey::Routes.new
-
741
@router = Journey::Router.new(@set, {
-
:parameters_key => PARAMETERS_KEY,
-
:request_class => request_class})
-
741
@formatter = Journey::Formatter.new @set
-
end
-
-
1
def draw(&block)
-
745
clear! unless @disable_clear_and_finalize
-
745
eval_block(block)
-
726
finalize! unless @disable_clear_and_finalize
-
nil
-
end
-
-
1
def append(&block)
-
3
@append << block
-
end
-
-
1
def prepend(&block)
-
@prepend << block
-
end
-
-
1
def eval_block(block)
-
748
if block.arity == 1
-
raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
-
1
"Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
-
end
-
747
mapper = Mapper.new(self)
-
747
if default_scope
-
1
mapper.with_default_scope(default_scope, &block)
-
else
-
746
mapper.instance_exec(&block)
-
end
-
end
-
-
1
def finalize!
-
726
return if @finalized
-
729
@append.each { |blk| eval_block(blk) }
-
726
@finalized = true
-
end
-
-
1
def clear!
-
746
@finalized = false
-
746
@url_helpers = nil
-
746
named_routes.clear
-
746
set.clear
-
746
formatter.clear
-
746
@prepend.each { |blk| eval_block(blk) }
-
end
-
-
1
module MountedHelpers #:nodoc:
-
1
extend ActiveSupport::Concern
-
1
include UrlFor
-
end
-
-
# Contains all the mounted helpers accross different
-
# engines and the `main_app` helper for the application.
-
# You can include this in your classes if you want to
-
# access routes for other engines.
-
1
def mounted_helpers
-
452
MountedHelpers
-
end
-
-
1
def define_mounted_helper(name)
-
6
return if MountedHelpers.method_defined?(name)
-
-
6
routes = self
-
6
MountedHelpers.class_eval do
-
6
define_method "_#{name}" do
-
10
RoutesProxy.new(routes, _routes_context)
-
end
-
end
-
-
6
MountedHelpers.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
-
def #{name}
-
@_#{name} ||= _#{name}
-
end
-
RUBY
-
end
-
-
1
def url_helpers
-
@url_helpers ||= begin
-
308
routes = self
-
-
308
Module.new do
-
308
extend ActiveSupport::Concern
-
308
include UrlFor
-
-
# Define url_for in the singleton level so one can do:
-
# Rails.application.routes.url_helpers.url_for(args)
-
308
@_routes = routes
-
308
class << self
-
308
delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
-
end
-
-
# Make named_routes available in the module singleton
-
# as well, so one can do:
-
# Rails.application.routes.url_helpers.posts_path
-
308
extend routes.named_routes.module
-
-
# Any class that includes this module will get all
-
# named routes...
-
308
include routes.named_routes.module
-
-
# plus a singleton class method called _routes ...
-
308
included do
-
1652
singleton_class.send(:redefine_method, :_routes) { routes }
-
end
-
-
# And an instance method _routes. Note that
-
# UrlFor (included in this module) add extra
-
# conveniences for working with @_routes.
-
4968
define_method(:_routes) { @_routes || routes }
-
end
-
733
end
-
end
-
-
1
def empty?
-
routes.empty?
-
end
-
-
1
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
-
8222
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
-
-
8217
path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
-
21959
conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
-
-
8217
route = @set.add_route(app, path, conditions, defaults, name)
-
8217
named_routes[name] = route if name && !named_routes[name]
-
8217
route
-
end
-
-
1
def build_path(path, requirements, separators, anchor)
-
8217
strexp = Journey::Router::Strexp.new(
-
path,
-
requirements,
-
SEPARATORS,
-
anchor)
-
-
8217
pattern = Journey::Path::Pattern.new(strexp)
-
-
8217
builder = Journey::GTG::Builder.new pattern.spec
-
-
# Get all the symbol nodes followed by literals that are not the
-
# dummy node.
-
8217
symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
-
13742
builder.followpos(n).first.literal?
-
}
-
-
# Get all the symbol nodes preceded by literals.
-
8217
symbols.concat pattern.spec.find_all(&:literal?).map { |n|
-
11372
builder.followpos(n).first
-
}.find_all(&:symbol?)
-
-
8217
symbols.each { |x|
-
4
x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
-
}
-
-
8217
pattern
-
end
-
1
private :build_path
-
-
1
def build_conditions(current_conditions, path_values)
-
8217
conditions = current_conditions.dup
-
-
# Rack-Mount requires that :request_method be a regular expression.
-
# :request_method represents the HTTP verb that matches this route.
-
#
-
# Here we munge values before they get sent on to rack-mount.
-
8217
verbs = conditions[:request_method] || []
-
8217
unless verbs.empty?
-
8185
conditions[:request_method] = %r[^#{verbs.join('|')}$]
-
end
-
-
8217
conditions.keep_if do |k, _|
-
8390
k == :action || k == :controller ||
-
@request_class.public_method_defined?(k) || path_values.include?(k)
-
end
-
end
-
1
private :build_conditions
-
-
1
class Generator #:nodoc:
-
1
PARAMETERIZE = lambda do |name, value|
-
6018
if name == :controller
-
2169
value
-
elsif value.is_a?(Array)
-
18
value.map { |v| v.to_param }.join('/')
-
elsif param = value.to_param
-
3835
param
-
end
-
end
-
-
1
attr_reader :options, :recall, :set, :named_route
-
-
1
def initialize(options, recall, set)
-
4010
@named_route = options.delete(:use_route)
-
4010
@options = options.dup
-
4010
@recall = recall.dup
-
4010
@set = set
-
-
4010
normalize_options!
-
4010
normalize_controller_action_id!
-
4010
use_relative_controller!
-
4010
normalize_controller!
-
4010
handle_nil_action!
-
end
-
-
1
def controller
-
8089
@options[:controller]
-
end
-
-
1
def current_controller
-
7095
@recall[:controller]
-
end
-
-
1
def use_recall_for(key)
-
6033
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
-
2595
if !named_route_exists? || segment_keys.include?(key)
-
2039
@options[key] = @recall.delete(key)
-
end
-
end
-
end
-
-
1
def normalize_options!
-
# If an explicit :controller was given, always make :action explicit
-
# too, so that action expiry works as expected for things like
-
#
-
# generate({controller: 'content'}, {controller: 'content', action: 'show'})
-
#
-
# (the above is from the unit tests). In the above case, because the
-
# controller was explicitly given, but no action, the action is implied to
-
# be "index", not the recalled action of "show".
-
-
4010
if options[:controller]
-
3035
options[:action] ||= 'index'
-
3035
options[:controller] = options[:controller].to_s
-
end
-
-
4010
if options[:action]
-
3932
options[:action] = options[:action].to_s
-
end
-
end
-
-
# This pulls :controller, :action, and :id out of the recall.
-
# The recall key is only used if there is no key in the options
-
# or if the key in the options is identical. If any of
-
# :controller, :action or :id is not found, don't pull any
-
# more keys from the recall.
-
1
def normalize_controller_action_id!
-
4010
@recall[:action] ||= 'index' if current_controller
-
-
4010
use_recall_for(:controller) or return
-
1032
use_recall_for(:action) or return
-
991
use_recall_for(:id)
-
end
-
-
# if the current controller is "foo/bar/baz" and controller: "baz/bat"
-
# is specified, the controller becomes "foo/baz/bat"
-
1
def use_relative_controller!
-
4010
if !named_route && different_controller? && !controller.start_with?("/")
-
20
old_parts = current_controller.split('/')
-
20
size = controller.count("/") + 1
-
20
parts = old_parts[0...-size] << controller
-
20
@options[:controller] = parts.join("/")
-
end
-
end
-
-
# Remove leading slashes from controllers
-
1
def normalize_controller!
-
4010
@options[:controller] = controller.sub(%r{^/}, '') if controller
-
end
-
-
# This handles the case of action: nil being explicitly passed.
-
# It is identical to action: "index"
-
1
def handle_nil_action!
-
4010
if options.has_key?(:action) && options[:action].nil?
-
options[:action] = 'index'
-
end
-
4010
recall[:action] = options.delete(:action) if options[:action] == 'index'
-
end
-
-
# Generates a path from routes, returns [path, params]
-
# if no path is returned the formatter will raise Journey::Router::RoutingError
-
1
def generate
-
4010
@set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
-
rescue Journey::Router::RoutingError => e
-
11
raise ActionController::UrlGenerationError, "No route matches #{options.inspect} #{e.message}"
-
end
-
-
1
def different_controller?
-
3036
return false unless current_controller
-
29
controller.to_param != current_controller.to_param
-
end
-
-
1
private
-
1
def named_route_exists?
-
2595
named_route && set.named_routes[named_route]
-
end
-
-
1
def segment_keys
-
556
set.named_routes[named_route].segment_keys
-
end
-
end
-
-
# Generate the path indicated by the arguments, and return an array of
-
# the keys that were not used to generate it.
-
1
def extra_keys(options, recall={})
-
1185
generate_extras(options, recall).last
-
end
-
-
1
def generate_extras(options, recall={})
-
1745
path, params = generate(options, recall)
-
1745
return path, params.keys
-
end
-
-
1
def generate(options, recall = {})
-
4010
Generator.new(options, recall, self).generate
-
end
-
-
1
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
-
:trailing_slash, :anchor, :params, :only_path, :script_name,
-
:original_script_name]
-
-
1
def mounted?
-
129
false
-
end
-
-
1
def optimize_routes_generation?
-
133
!mounted? && default_url_options.empty?
-
end
-
-
1
def _generate_prefix(options = {})
-
nil
-
end
-
-
# The +options+ argument must be +nil+ or a hash whose keys are *symbols*.
-
1
def url_for(options)
-
2265
options = default_url_options.merge(options || {})
-
-
2265
user, password = extract_authentication(options)
-
2265
recall = options.delete(:_recall)
-
-
2265
original_script_name = options.delete(:original_script_name).presence
-
2265
script_name = options.delete(:script_name).presence || _generate_prefix(options)
-
-
2265
if script_name && original_script_name
-
script_name = original_script_name + script_name
-
end
-
-
2265
path_options = options.except(*RESERVED_OPTIONS)
-
2265
path_options = yield(path_options) if block_given?
-
-
2265
path, params = generate(path_options, recall || {})
-
2254
params.merge!(options[:params] || {})
-
-
2254
ActionDispatch::Http::URL.url_for(options.merge!({
-
:path => path,
-
:script_name => script_name,
-
:params => params,
-
:user => user,
-
:password => password
-
}))
-
end
-
-
1
def call(env)
-
745
@router.call(env)
-
end
-
-
1
def recognize_path(path, environment = {})
-
1985
method = (environment[:method] || "GET").to_s.upcase
-
1985
path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
-
1985
extras = environment[:extras] || {}
-
-
1985
begin
-
1985
env = Rack::MockRequest.env_for(path, {:method => method})
-
rescue URI::InvalidURIError => e
-
1
raise ActionController::RoutingError, e.message
-
end
-
-
1984
req = @request_class.new(env)
-
1984
@router.recognize(req) do |route, matches, params|
-
1816
params.merge!(extras)
-
1816
params.each do |key, value|
-
5711
if value.is_a?(String)
-
5696
value = value.dup.force_encoding(Encoding::BINARY)
-
5696
params[key] = URI.parser.unescape(value)
-
end
-
end
-
1816
old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
-
1816
env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params)
-
1816
dispatcher = route.app
-
1816
while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
-
4
dispatcher = dispatcher.app
-
end
-
-
1816
if dispatcher.is_a?(Dispatcher)
-
1813
if dispatcher.controller(params, false)
-
1805
dispatcher.prepare_params!(params)
-
1805
return params
-
else
-
8
raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller"
-
end
-
end
-
end
-
-
170
raise ActionController::RoutingError, "No route matches #{path.inspect}"
-
end
-
-
1
private
-
-
1
def extract_authentication(options)
-
2265
if options[:user] && options[:password]
-
2
[options.delete(:user), options.delete(:password)]
-
else
-
nil
-
end
-
end
-
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
module Routing
-
1
class RoutesProxy #:nodoc:
-
1
include ActionDispatch::Routing::UrlFor
-
-
1
attr_accessor :scope, :routes
-
1
alias :_routes :routes
-
-
1
def initialize(routes, scope)
-
10
@routes, @scope = routes, scope
-
end
-
-
1
def url_options
-
8
scope.send(:_with_routes, routes) do
-
8
scope.url_options
-
end
-
end
-
-
1
def respond_to?(method, include_private = false)
-
1
super || routes.url_helpers.respond_to?(method)
-
end
-
-
1
def method_missing(method, *args)
-
4
if routes.url_helpers.respond_to?(method)
-
4
self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{method}(*args)
-
options = args.extract_options!
-
args << url_options.merge((options || {}).symbolize_keys)
-
routes.url_helpers.#{method}(*args)
-
end
-
RUBY
-
4
send(method, *args)
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
module Routing
-
# In <tt>config/routes.rb</tt> you define URL-to-controller mappings, but the reverse
-
# is also possible: an URL can be generated from one of your routing definitions.
-
# URL generation functionality is centralized in this module.
-
#
-
# See ActionDispatch::Routing for general information about routing and routes.rb.
-
#
-
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
-
# then ActionController::UrlFor is what you're looking for. Read on for
-
# an introduction. In general, this module should not be included on its own,
-
# as it is usually included by url_helpers (as in Rails.application.routes.url_helpers).
-
#
-
# == URL generation from parameters
-
#
-
# As you may know, some functions, such as ActionController::Base#url_for
-
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
-
# of parameters. For example, you've probably had the chance to write code
-
# like this in one of your views:
-
#
-
# <%= link_to('Click here', controller: 'users',
-
# action: 'new', message: 'Welcome!') %>
-
# # => "/users/new?message=Welcome%21"
-
#
-
# link_to, and all other functions that require URL generation functionality,
-
# actually use ActionController::UrlFor under the hood. And in particular,
-
# they use the ActionController::UrlFor#url_for method. One can generate
-
# the same path as the above example by using the following code:
-
#
-
# include UrlFor
-
# url_for(controller: 'users',
-
# action: 'new',
-
# message: 'Welcome!',
-
# only_path: true)
-
# # => "/users/new?message=Welcome%21"
-
#
-
# Notice the <tt>only_path: true</tt> part. This is because UrlFor has no
-
# information about the website hostname that your Rails app is serving. So if you
-
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
-
# argument:
-
#
-
# include UrlFor
-
# url_for(controller: 'users',
-
# action: 'new',
-
# message: 'Welcome!',
-
# host: 'www.example.com')
-
# # => "http://www.example.com/users/new?message=Welcome%21"
-
#
-
# By default, all controllers and views have access to a special version of url_for,
-
# that already knows what the current hostname is. So if you use url_for in your
-
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
-
# argument.
-
#
-
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
-
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
-
# in full. However, mailers don't have hostname information, and that's why you'll still
-
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
-
#
-
#
-
# == URL generation for named routes
-
#
-
# UrlFor also allows one to access methods that have been auto-generated from
-
# named routes. For example, suppose that you have a 'users' resource in your
-
# <tt>config/routes.rb</tt>:
-
#
-
# resources :users
-
#
-
# This generates, among other things, the method <tt>users_path</tt>. By default,
-
# this method is accessible from your controllers, views and mailers. If you need
-
# to access this auto-generated method from other places (such as a model), then
-
# you can do that by including Rails.application.routes.url_helpers in your class:
-
#
-
# class User < ActiveRecord::Base
-
# include Rails.application.routes.url_helpers
-
#
-
# def base_uri
-
# user_path(self)
-
# end
-
# end
-
#
-
# User.find(1).base_uri # => "/users/1"
-
#
-
1
module UrlFor
-
1
extend ActiveSupport::Concern
-
1
include PolymorphicRoutes
-
-
1
included do
-
228
unless method_defined?(:default_url_options)
-
# Including in a class uses an inheritable hash. Modules get a plain hash.
-
227
if respond_to?(:class_attribute)
-
226
class_attribute :default_url_options
-
else
-
1
mattr_writer :default_url_options
-
end
-
-
227
self.default_url_options = {}
-
end
-
-
228
include(*_url_for_modules) if respond_to?(:_url_for_modules)
-
end
-
-
1
def initialize(*)
-
6105
@_routes = nil
-
6105
super
-
end
-
-
# Hook overridden in controller to add request information
-
# with `default_url_options`. Application logic should not
-
# go into url_options.
-
1
def url_options
-
1175
default_url_options
-
end
-
-
# Generate a url based on the options provided, default_url_options and the
-
# routes defined in routes.rb. The following options are supported:
-
#
-
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
-
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
-
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
-
# If <tt>:only_path</tt> is false, this option must be
-
# provided either explicitly, or via +default_url_options+.
-
# * <tt>:subdomain</tt> - Specifies the subdomain of the link, using the +tld_length+
-
# to split the subdomain from the host.
-
# If false, removes all subdomains from the host part of the link.
-
# * <tt>:domain</tt> - Specifies the domain of the link, using the +tld_length+
-
# to split the domain from the host.
-
# * <tt>:tld_length</tt> - Number of labels the TLD id composed of, only used if
-
# <tt>:subdomain</tt> or <tt>:domain</tt> are supplied. Defaults to
-
# <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1.
-
# * <tt>:port</tt> - Optionally specify the port to connect to.
-
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
-
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
-
#
-
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
-
# +url_for+ is forwarded to the Routes module.
-
#
-
# url_for controller: 'tasks', action: 'testing', host: 'somehost.org', port: '8080'
-
# # => 'http://somehost.org:8080/tasks/testing'
-
# url_for controller: 'tasks', action: 'testing', host: 'somehost.org', anchor: 'ok', only_path: true
-
# # => '/tasks/testing#ok'
-
# url_for controller: 'tasks', action: 'testing', trailing_slash: true
-
# # => 'http://somehost.org/tasks/testing/'
-
# url_for controller: 'tasks', action: 'testing', host: 'somehost.org', number: '33'
-
# # => 'http://somehost.org/tasks/testing?number=33'
-
1
def url_for(options = nil)
-
1133
case options
-
when nil
-
1
_routes.url_for(url_options.symbolize_keys)
-
when Hash
-
1109
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
-
when String
-
8
options
-
else
-
15
polymorphic_url(options)
-
end
-
end
-
-
1
protected
-
-
1
def optimize_routes_generation?
-
213
return @_optimized_routes if defined?(@_optimized_routes)
-
131
@_optimized_routes = _routes.optimize_routes_generation? && default_url_options.empty?
-
end
-
-
1
def _with_routes(routes)
-
8
old_routes, @_routes = @_routes, routes
-
8
yield
-
ensure
-
8
@_routes = old_routes
-
end
-
-
1
def _routes_context
-
10
self
-
end
-
end
-
end
-
end
-
1
module ActionDispatch
-
1
module Assertions
-
1
autoload :DomAssertions, 'action_dispatch/testing/assertions/dom'
-
1
autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response'
-
1
autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing'
-
1
autoload :SelectorAssertions, 'action_dispatch/testing/assertions/selector'
-
1
autoload :TagAssertions, 'action_dispatch/testing/assertions/tag'
-
-
1
extend ActiveSupport::Concern
-
-
1
include DomAssertions
-
1
include ResponseAssertions
-
1
include RoutingAssertions
-
1
include SelectorAssertions
-
1
include TagAssertions
-
end
-
end
-
-
1
require 'action_view/vendor/html-scanner'
-
-
1
module ActionDispatch
-
1
module Assertions
-
1
module DomAssertions
-
# \Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
-
#
-
# # assert that the referenced method generates the appropriate HTML string
-
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
-
1
def assert_dom_equal(expected, actual, message = "")
-
1053
expected_dom = HTML::Document.new(expected).root
-
1053
actual_dom = HTML::Document.new(actual).root
-
1053
assert_equal expected_dom, actual_dom
-
end
-
-
# The negated form of +assert_dom_equivalent+.
-
#
-
# # assert that the referenced method does not generate the specified HTML string
-
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
-
1
def assert_dom_not_equal(expected, actual, message = "")
-
expected_dom = HTML::Document.new(expected).root
-
actual_dom = HTML::Document.new(actual).root
-
refute_equal expected_dom, actual_dom
-
end
-
end
-
end
-
end
-
-
1
module ActionDispatch
-
1
module Assertions
-
# A small suite of assertions that test responses from \Rails applications.
-
1
module ResponseAssertions
-
# Asserts that the response is one of the following types:
-
#
-
# * <tt>:success</tt> - Status code was in the 200-299 range
-
# * <tt>:redirect</tt> - Status code was in the 300-399 range
-
# * <tt>:missing</tt> - Status code was 404
-
# * <tt>:error</tt> - Status code was in the 500-599 range
-
#
-
# You can also pass an explicit status number like <tt>assert_response(501)</tt>
-
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
-
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
-
#
-
# # assert that the response was a redirection
-
# assert_response :redirect
-
#
-
# # assert that the response code was status code 401 (unauthorized)
-
# assert_response 401
-
1
def assert_response(type, message = nil)
-
535
message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
-
-
535
if Symbol === type
-
481
if [:success, :missing, :redirect, :error].include?(type)
-
268
assert @response.send("#{type}?"), message
-
else
-
213
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
-
213
assert_equal code, @response.response_code, message
-
end
-
else
-
54
assert_equal type, @response.response_code, message
-
end
-
end
-
-
# Assert that the redirection options passed in match those of the redirect called in the latest action.
-
# This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also
-
# match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on.
-
#
-
# # assert that the redirection was to the "index" action on the WeblogController
-
# assert_redirected_to controller: "weblog", action: "index"
-
#
-
# # assert that the redirection was to the named route login_url
-
# assert_redirected_to login_url
-
#
-
# # assert that the redirection was to the url for @customer
-
# assert_redirected_to @customer
-
#
-
# # asserts that the redirection matches the regular expression
-
# assert_redirected_to %r(\Ahttp://example.org)
-
1
def assert_redirected_to(options = {}, message=nil)
-
32
assert_response(:redirect, message)
-
31
return true if options === @response.location
-
-
23
redirect_is = normalize_argument_to_redirection(@response.location)
-
23
redirect_expected = normalize_argument_to_redirection(options)
-
-
23
message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
-
23
assert_operator redirect_expected, :===, redirect_is, message
-
end
-
-
1
private
-
# Proxy to to_param if the object will respond to it.
-
1
def parameterize(value)
-
value.respond_to?(:to_param) ? value.to_param : value
-
end
-
-
1
def normalize_argument_to_redirection(fragment)
-
46
normalized = case fragment
-
when Regexp
-
1
fragment
-
when %r{^\w[A-Za-z\d+.-]*:.*}
-
25
fragment
-
when String
-
7
@request.protocol + @request.host_with_port + fragment
-
when :back
-
raise RedirectBackError unless refer = @request.headers["Referer"]
-
refer
-
else
-
13
@controller.url_for(fragment)
-
end
-
-
46
normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized
-
end
-
end
-
end
-
end
-
1
require 'uri'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'action_controller/metal/exceptions'
-
-
1
module ActionDispatch
-
1
module Assertions
-
# Suite of assertions to test routes generated by \Rails and the handling of requests made to them.
-
1
module RoutingAssertions
-
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
-
# match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+.
-
#
-
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
-
# requiring a specific HTTP method. The hash should contain a :path with the incoming request path
-
# and a :method containing the required HTTP verb.
-
#
-
# # assert that POSTing to /items will call the create action on ItemsController
-
# assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
-
#
-
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
-
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
-
# extras argument, appending the query string on the path directly will not work. For example:
-
#
-
# # assert that a path of '/items/list/1?view=print' returns the correct options
-
# assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
-
#
-
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
-
#
-
# # Check the default route (i.e., the index action)
-
# assert_recognizes({controller: 'items', action: 'index'}, 'items')
-
#
-
# # Test a specific action
-
# assert_recognizes({controller: 'items', action: 'list'}, 'items/list')
-
#
-
# # Test an action with a parameter
-
# assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1')
-
#
-
# # Test a custom route
-
# assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
-
1
def assert_recognizes(expected_options, path, extras={}, message=nil)
-
1806
request = recognized_request_for(path, extras)
-
-
1642
expected_options = expected_options.clone
-
-
1642
expected_options.stringify_keys!
-
-
message ||= sprintf("The recognized options <%s> did not match <%s>, difference: <%s>",
-
1642
request.path_parameters, expected_options, diff(expected_options, request.path_parameters))
-
1642
assert_equal(expected_options, request.path_parameters, message)
-
end
-
-
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
-
# The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
-
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
-
#
-
# The +defaults+ parameter is unused.
-
#
-
# # Asserts that the default action is generated for a route with no action
-
# assert_generates "/items", controller: "items", action: "index"
-
#
-
# # Tests that the list action is properly routed
-
# assert_generates "/items/list", controller: "items", action: "list"
-
#
-
# # Tests the generation of a route with a parameter
-
# assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" }
-
#
-
# # Asserts that the generated route gives us our custom route
-
# assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
-
1
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
-
543
if expected_path =~ %r{://}
-
2
fail_on(URI::InvalidURIError) do
-
2
uri = URI.parse(expected_path)
-
2
expected_path = uri.path.to_s.empty? ? "/" : uri.path
-
end
-
else
-
541
expected_path = "/#{expected_path}" unless expected_path.first == '/'
-
end
-
# Load routes.rb if it hasn't been loaded.
-
-
543
generated_path, extra_keys = @routes.generate_extras(options, defaults)
-
2259
found_extras = options.reject {|k, v| ! extra_keys.include? k}
-
-
543
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
-
543
assert_equal(extras, found_extras, msg)
-
-
543
msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path,
-
expected_path)
-
543
assert_equal(expected_path, generated_path, msg)
-
end
-
-
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
-
# <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
-
# and +assert_generates+ into one step.
-
#
-
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
-
# +message+ parameter allows you to specify a custom error message to display upon failure.
-
#
-
# # Assert a basic route: a controller with the default action (index)
-
# assert_routing '/home', controller: 'home', action: 'index'
-
#
-
# # Test a route generated with a specific controller, action, and parameter (id)
-
# assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23
-
#
-
# # Assert a basic route (controller + default action), with an error message if it fails
-
# assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly'
-
#
-
# # Tests a route, providing a defaults hash
-
# assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"}
-
#
-
# # Tests a route with a HTTP method
-
# assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" })
-
1
def assert_routing(path, options, defaults={}, extras={}, message=nil)
-
536
assert_recognizes(options, path, extras, message)
-
-
533
controller, default_controller = options[:controller], defaults[:controller]
-
533
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
-
options[:controller] = "/#{controller}"
-
end
-
-
2228
generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) }
-
533
assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
-
end
-
-
# A helper to make it easier to test different route configurations.
-
# This method temporarily replaces @routes
-
# with a new RouteSet instance.
-
#
-
# The new instance is yielded to the passed block. Typically the block
-
# will create some routes using <tt>set.draw { match ... }</tt>:
-
#
-
# with_routing do |set|
-
# set.draw do
-
# resources :users
-
# end
-
# assert_equal "/users", users_path
-
# end
-
#
-
1
def with_routing
-
141
old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
-
141
if defined?(@controller) && @controller
-
56
old_controller, @controller = @controller, @controller.clone
-
56
_routes = @routes
-
-
# Unfortunately, there is currently an abstraction leak between AC::Base
-
# and AV::Base which requires having the URL helpers in both AC and AV.
-
# To do this safely at runtime for tests, we need to bump up the helper serial
-
# to that the old AV subclass isn't cached.
-
#
-
# TODO: Make this unnecessary
-
56
@controller.singleton_class.send(:include, _routes.url_helpers)
-
56
@controller.view_context_class = Class.new(@controller.view_context_class) do
-
56
include _routes.url_helpers
-
end
-
end
-
141
yield @routes
-
ensure
-
141
@routes = old_routes
-
141
if defined?(@controller) && @controller
-
104
@controller = old_controller
-
end
-
end
-
-
# ROUTES TODO: These assertions should really work in an integration context
-
1
def method_missing(selector, *args, &block)
-
26
if defined?(@controller) && @controller && @routes && @routes.named_routes.helpers.include?(selector)
-
5
@controller.send(selector, *args, &block)
-
else
-
21
super
-
end
-
end
-
-
1
private
-
# Recognizes the route for a given path.
-
1
def recognized_request_for(path, extras = {})
-
1806
if path.is_a?(Hash)
-
1257
method = path[:method]
-
1257
path = path[:path]
-
else
-
549
method = :get
-
end
-
-
# Assume given controller
-
1806
request = ActionController::TestRequest.new
-
-
1806
if path =~ %r{://}
-
8
fail_on(URI::InvalidURIError) do
-
8
uri = URI.parse(path)
-
8
request.env["rack.url_scheme"] = uri.scheme || "http"
-
8
request.host = uri.host if uri.host
-
8
request.port = uri.port if uri.port
-
8
request.path = uri.path.to_s.empty? ? "/" : uri.path
-
end
-
else
-
1798
path = "/#{path}" unless path.first == "/"
-
1798
request.path = path
-
end
-
-
1806
request.request_method = method if method
-
-
1806
params = fail_on(ActionController::RoutingError) do
-
1806
@routes.recognize_path(path, { :method => method, :extras => extras })
-
end
-
1642
request.path_parameters = params.with_indifferent_access
-
-
1642
request
-
end
-
-
1
def fail_on(exception_class)
-
1816
begin
-
1816
yield
-
164
rescue exception_class => e
-
164
raise MiniTest::Assertion, e.message
-
end
-
end
-
end
-
end
-
end
-
1
require 'action_view/vendor/html-scanner'
-
1
require 'active_support/core_ext/object/inclusion'
-
-
#--
-
# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
-
# Under MIT and/or CC By license.
-
#++
-
-
1
module ActionDispatch
-
1
module Assertions
-
1
NO_STRIP = %w{pre script style textarea}
-
-
# Adds the +assert_select+ method for use in Rails functional
-
# test cases, which can be used to make assertions on the response HTML of a controller
-
# action. You can also call +assert_select+ within another +assert_select+ to
-
# make assertions on elements selected by the enclosing assertion.
-
#
-
# Use +css_select+ to select elements without making an assertions, either
-
# from the response HTML or elements selected by the enclosing assertion.
-
#
-
# In addition to HTML responses, you can make the following assertions:
-
#
-
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
-
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
-
#
-
# Also see HTML::Selector to learn how to use selectors.
-
1
module SelectorAssertions
-
# Select and return all matching elements.
-
#
-
# If called with a single argument, uses that argument as a selector
-
# to match all elements of the current page. Returns an empty array
-
# if no match is found.
-
#
-
# If called with two arguments, uses the first argument as the base
-
# element and the second argument as the selector. Attempts to match the
-
# base element and any of its children. Returns an empty array if no
-
# match is found.
-
#
-
# The selector may be a CSS selector expression (String), an expression
-
# with substitution values (Array) or an HTML::Selector object.
-
#
-
# # Selects all div tags
-
# divs = css_select("div")
-
#
-
# # Selects all paragraph tags and does something interesting
-
# pars = css_select("p")
-
# pars.each do |par|
-
# # Do something fun with paragraphs here...
-
# end
-
#
-
# # Selects all list items in unordered lists
-
# items = css_select("ul>li")
-
#
-
# # Selects all form tags and then all inputs inside the form
-
# forms = css_select("form")
-
# forms.each do |form|
-
# inputs = css_select(form, "input")
-
# ...
-
# end
-
1
def css_select(*args)
-
# See assert_select to understand what's going on here.
-
38
arg = args.shift
-
-
38
if arg.is_a?(HTML::Node)
-
28
root = arg
-
28
arg = args.shift
-
elsif arg == nil
-
raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
-
elsif defined?(@selected) && @selected
-
8
matches = []
-
-
8
@selected.each do |selected|
-
16
subset = css_select(selected, HTML::Selector.new(arg.dup, args.dup))
-
16
subset.each do |match|
-
16
matches << match unless matches.any? { |m| m.equal?(match) }
-
end
-
end
-
-
8
return matches
-
else
-
2
root = response_from_page
-
end
-
-
30
case arg
-
when String
-
14
selector = HTML::Selector.new(arg, args)
-
when Array
-
selector = HTML::Selector.new(*arg)
-
when HTML::Selector
-
16
selector = arg
-
else raise ArgumentError, "Expecting a selector as the first argument"
-
end
-
-
30
selector.select(root)
-
end
-
-
# An assertion that selects elements and makes one or more equality tests.
-
#
-
# If the first argument is an element, selects all matching elements
-
# starting from (and including) that element and all its children in
-
# depth-first order.
-
#
-
# If no element if specified, calling +assert_select+ selects from the
-
# response HTML unless +assert_select+ is called from within an +assert_select+ block.
-
#
-
# When called with a block +assert_select+ passes an array of selected elements
-
# to the block. Calling +assert_select+ from the block, with no element specified,
-
# runs the assertion on the complete set of elements selected by the enclosing assertion.
-
# Alternatively the array may be iterated through so that +assert_select+ can be called
-
# separately for each element.
-
#
-
#
-
# ==== Example
-
# If the response contains two ordered lists, each with four list elements then:
-
# assert_select "ol" do |elements|
-
# elements.each do |element|
-
# assert_select element, "li", 4
-
# end
-
# end
-
#
-
# will pass, as will:
-
# assert_select "ol" do
-
# assert_select "li", 8
-
# end
-
#
-
# The selector may be a CSS selector expression (String), an expression
-
# with substitution values, or an HTML::Selector object.
-
#
-
# === Equality Tests
-
#
-
# The equality test may be one of the following:
-
# * <tt>true</tt> - Assertion is true if at least one element selected.
-
# * <tt>false</tt> - Assertion is true if no element selected.
-
# * <tt>String/Regexp</tt> - Assertion is true if the text value of at least
-
# one element matches the string or regular expression.
-
# * <tt>Integer</tt> - Assertion is true if exactly that number of
-
# elements are selected.
-
# * <tt>Range</tt> - Assertion is true if the number of selected
-
# elements fit the range.
-
# If no equality test specified, the assertion is true if at least one
-
# element selected.
-
#
-
# To perform more than one equality tests, use a hash with the following keys:
-
# * <tt>:text</tt> - Narrow the selection to elements that have this text
-
# value (string or regexp).
-
# * <tt>:html</tt> - Narrow the selection to elements that have this HTML
-
# content (string or regexp).
-
# * <tt>:count</tt> - Assertion is true if the number of selected elements
-
# is equal to this value.
-
# * <tt>:minimum</tt> - Assertion is true if the number of selected
-
# elements is at least this value.
-
# * <tt>:maximum</tt> - Assertion is true if the number of selected
-
# elements is at most this value.
-
#
-
# If the method is called with a block, once all equality tests are
-
# evaluated the block is called with an array of all matched elements.
-
#
-
# ==== Examples
-
#
-
# # At least one form element
-
# assert_select "form"
-
#
-
# # Form element includes four input fields
-
# assert_select "form input", 4
-
#
-
# # Page title is "Welcome"
-
# assert_select "title", "Welcome"
-
#
-
# # Page title is "Welcome" and there is only one title element
-
# assert_select "title", {count: 1, text: "Welcome"},
-
# "Wrong title or more than one title element"
-
#
-
# # Page contains no forms
-
# assert_select "form", false, "This page must contain no forms"
-
#
-
# # Test the content and style
-
# assert_select "body div.header ul.menu"
-
#
-
# # Use substitution values
-
# assert_select "ol>li#?", /item-\d+/
-
#
-
# # All input fields in the form have a name
-
# assert_select "form input" do
-
# assert_select "[name=?]", /.+/ # Not empty
-
# end
-
1
def assert_select(*args, &block)
-
# Start with optional element followed by mandatory selector.
-
222
arg = args.shift
-
222
@selected ||= nil
-
-
222
if arg.is_a?(HTML::Node)
-
# First argument is a node (tag or text, but also HTML root),
-
# so we know what we're selecting from.
-
4
root = arg
-
4
arg = args.shift
-
elsif arg == nil
-
# This usually happens when passing a node/element that
-
# happens to be nil.
-
raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
-
elsif @selected
-
47
root = HTML::Node.new(nil)
-
47
root.children.concat @selected
-
else
-
# Otherwise just operate on the response document.
-
171
root = response_from_page
-
end
-
-
# First or second argument is the selector: string and we pass
-
# all remaining arguments. Array and we pass the argument. Also
-
# accepts selector itself.
-
222
case arg
-
when String
-
222
selector = HTML::Selector.new(arg, args)
-
when Array
-
selector = HTML::Selector.new(*arg)
-
when HTML::Selector
-
selector = arg
-
else raise ArgumentError, "Expecting a selector as the first argument"
-
end
-
-
# Next argument is used for equality tests.
-
222
equals = {}
-
222
case arg = args.shift
-
when Hash
-
55
equals = arg
-
when String, Regexp
-
45
equals[:text] = arg
-
when Integer
-
8
equals[:count] = arg
-
when Range
-
2
equals[:minimum] = arg.begin
-
2
equals[:maximum] = arg.end
-
when FalseClass
-
6
equals[:count] = 0
-
when NilClass, TrueClass
-
106
equals[:minimum] = 1
-
else raise ArgumentError, "I don't understand what you're trying to match"
-
end
-
-
# By default we're looking for at least one match.
-
222
if equals[:count]
-
39
equals[:minimum] = equals[:maximum] = equals[:count]
-
else
-
183
equals[:minimum] = 1 unless equals[:minimum]
-
end
-
-
# Last argument is the message we use if the assertion fails.
-
222
message = args.shift
-
#- message = "No match made with selector #{selector.inspect}" unless message
-
222
if args.shift
-
raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
-
end
-
-
222
matches = selector.select(root)
-
# If text/html, narrow down to those elements that match it.
-
222
content_mismatch = nil
-
222
if match_with = equals[:text]
-
65
matches.delete_if do |match|
-
102
text = ""
-
102
stack = match.children.reverse
-
102
while node = stack.pop
-
150
if node.tag?
-
28
stack.concat node.children.reverse
-
else
-
122
content = node.content
-
122
text << content
-
end
-
end
-
102
text.strip! unless NO_STRIP.include?(match.name)
-
102
text.sub!(/\A\n/, '') if match.name == "textarea"
-
102
unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s)
-
35
content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, text)
-
35
true
-
end
-
end
-
elsif match_with = equals[:html]
-
9
matches.delete_if do |match|
-
14
html = match.children.map(&:to_s).join
-
14
html.strip! unless NO_STRIP.include?(match.name)
-
14
unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s)
-
5
content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, html)
-
5
true
-
end
-
end
-
end
-
# Expecting foo found bar element only if found zero, not if
-
# found one but expecting two.
-
222
message ||= content_mismatch if matches.empty?
-
# Test minimum/maximum occurrence.
-
222
min, max, count = equals[:minimum], equals[:maximum], equals[:count]
-
-
# FIXME: minitest provides messaging when we use assert_operator,
-
# so is this custom message really needed?
-
222
message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}.)
-
222
if count
-
39
assert_equal matches.size, count, message
-
else
-
183
assert_operator matches.size, :>=, min, message if min
-
167
assert_operator matches.size, :<=, max, message if max
-
end
-
-
# If a block is given call that block. Set @selected to allow
-
# nested assert_select, which can be nested several levels deep.
-
197
if block_given? && !matches.empty?
-
32
begin
-
32
in_scope, @selected = @selected, matches
-
32
yield matches
-
ensure
-
32
@selected = in_scope
-
end
-
end
-
-
# Returns all matches elements.
-
196
matches
-
end
-
-
1
def count_description(min, max, count) #:nodoc:
-
422
pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')}
-
-
214
if min && max && (max != min)
-
6
"between #{min} and #{max} elements"
-
208
elsif min && max && max == min && count
-
39
"exactly #{count} #{pluralize['element', min]}"
-
169
elsif min && !(min == 1 && max == 1)
-
168
"at least #{min} #{pluralize['element', min]}"
-
1
elsif max
-
1
"at most #{max} #{pluralize['element', max]}"
-
end
-
end
-
-
# Extracts the content of an element, treats it as encoded HTML and runs
-
# nested assertion on it.
-
#
-
# You typically call this method within another assertion to operate on
-
# all currently selected elements. You can also pass an element or array
-
# of elements.
-
#
-
# The content of each element is un-encoded, and wrapped in the root
-
# element +encoded+. It then calls the block with all un-encoded elements.
-
#
-
# # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)
-
# assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do
-
# # Select each entry item and then the title item
-
# assert_select "entry>title" do
-
# # Run assertions on the encoded title elements
-
# assert_select_encoded do
-
# assert_select "b"
-
# end
-
# end
-
# end
-
#
-
#
-
# # Selects all paragraph tags from within the description of an RSS feed
-
# assert_select "rss[version=2.0]" do
-
# # Select description element of each feed item.
-
# assert_select "channel>item>description" do
-
# # Run assertions on the encoded elements.
-
# assert_select_encoded do
-
# assert_select "p"
-
# end
-
# end
-
# end
-
1
def assert_select_encoded(element = nil, &block)
-
6
case element
-
when Array
-
elements = element
-
when HTML::Node
-
2
elements = [element]
-
when nil
-
4
unless elements = @selected
-
raise ArgumentError, "First argument is optional, but must be called from a nested assert_select"
-
end
-
else
-
raise ArgumentError, "Argument is optional, and may be node or array of nodes"
-
end
-
-
6
fix_content = lambda do |node|
-
# Gets around a bug in the Rails 1.1 HTML parser.
-
28
node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) }
-
end
-
-
6
selected = elements.map do |_element|
-
68
text = _element.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
-
10
root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root
-
10
css_select(root, "encoded:root", &block)[0]
-
end
-
-
6
begin
-
6
old_selected, @selected = @selected, selected
-
6
assert_select ":root", &block
-
ensure
-
6
@selected = old_selected
-
end
-
end
-
-
# Extracts the body of an email and runs nested assertions on it.
-
#
-
# You must enable deliveries for this assertion to work, use:
-
# ActionMailer::Base.perform_deliveries = true
-
#
-
# assert_select_email do
-
# assert_select "h1", "Email alert"
-
# end
-
#
-
# assert_select_email do
-
# items = assert_select "ol>li"
-
# items.each do
-
# # Work with items here...
-
# end
-
# end
-
1
def assert_select_email(&block)
-
3
deliveries = ActionMailer::Base.deliveries
-
3
assert !deliveries.empty?, "No e-mail in delivery list"
-
-
2
deliveries.each do |delivery|
-
2
(delivery.parts.empty? ? [delivery] : delivery.parts).each do |part|
-
3
if part["Content-Type"].to_s =~ /^text\/html\W/
-
2
root = HTML::Document.new(part.body.to_s).root
-
2
assert_select root, ":root", &block
-
end
-
end
-
end
-
end
-
-
1
protected
-
# +assert_select+ and +css_select+ call this to obtain the content in the HTML page.
-
1
def response_from_page
-
90
html_document.root
-
end
-
end
-
end
-
end
-
1
require 'action_view/vendor/html-scanner'
-
-
1
module ActionDispatch
-
1
module Assertions
-
# Pair of assertions to testing elements in the HTML output of the response.
-
1
module TagAssertions
-
# Asserts that there is a tag/node/element in the body of the response
-
# that meets all of the given conditions. The +conditions+ parameter must
-
# be a hash of any of the following keys (all are optional):
-
#
-
# * <tt>:tag</tt>: the node type must match the corresponding value
-
# * <tt>:attributes</tt>: a hash. The node's attributes must match the
-
# corresponding values in the hash.
-
# * <tt>:parent</tt>: a hash. The node's parent must match the
-
# corresponding hash.
-
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
-
# must meet the criteria described by the hash.
-
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
-
# meet the criteria described by the hash.
-
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
-
# must meet the criteria described by the hash.
-
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
-
# meet the criteria described by the hash.
-
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
-
# the criteria described by the hash, and at least one sibling must match.
-
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
-
# the criteria described by the hash, and at least one sibling must match.
-
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts
-
# the keys:
-
# * <tt>:count</tt>: either a number or a range which must equal (or
-
# include) the number of children that match.
-
# * <tt>:less_than</tt>: the number of matching children must be less
-
# than this number.
-
# * <tt>:greater_than</tt>: the number of matching children must be
-
# greater than this number.
-
# * <tt>:only</tt>: another hash consisting of the keys to use
-
# to match on the children, and only matching children will be
-
# counted.
-
# * <tt>:content</tt>: the textual content of the node must match the
-
# given value. This will not match HTML tags in the body of a
-
# tag--only text.
-
#
-
# Conditions are matched using the following algorithm:
-
#
-
# * if the condition is a string, it must be a substring of the value.
-
# * if the condition is a regexp, it must match the value.
-
# * if the condition is a number, the value must match number.to_s.
-
# * if the condition is +true+, the value must not be +nil+.
-
# * if the condition is +false+ or +nil+, the value must be +nil+.
-
#
-
# # Assert that there is a "span" tag
-
# assert_tag tag: "span"
-
#
-
# # Assert that there is a "span" tag with id="x"
-
# assert_tag tag: "span", attributes: { id: "x" }
-
#
-
# # Assert that there is a "span" tag using the short-hand
-
# assert_tag :span
-
#
-
# # Assert that there is a "span" tag with id="x" using the short-hand
-
# assert_tag :span, attributes: { id: "x" }
-
#
-
# # Assert that there is a "span" inside of a "div"
-
# assert_tag tag: "span", parent: { tag: "div" }
-
#
-
# # Assert that there is a "span" somewhere inside a table
-
# assert_tag tag: "span", ancestor: { tag: "table" }
-
#
-
# # Assert that there is a "span" with at least one "em" child
-
# assert_tag tag: "span", child: { tag: "em" }
-
#
-
# # Assert that there is a "span" containing a (possibly nested)
-
# # "strong" tag.
-
# assert_tag tag: "span", descendant: { tag: "strong" }
-
#
-
# # Assert that there is a "span" containing between 2 and 4 "em" tags
-
# # as immediate children
-
# assert_tag tag: "span",
-
# children: { count: 2..4, only: { tag: "em" } }
-
#
-
# # Get funky: assert that there is a "div", with an "ul" ancestor
-
# # and an "li" parent (with "class" = "enum"), and containing a
-
# # "span" descendant that contains text matching /hello world/
-
# assert_tag tag: "div",
-
# ancestor: { tag: "ul" },
-
# parent: { tag: "li",
-
# attributes: { class: "enum" } },
-
# descendant: { tag: "span",
-
# child: /hello world/ }
-
#
-
# <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work
-
# with well-formed XHTML. They recognize a few tags as implicitly self-closing
-
# (like br and hr and such) but will not work correctly with tags
-
# that allow optional closing tags (p, li, td). <em>You must explicitly
-
# close all of your tags to use these assertions.</em>
-
1
def assert_tag(*opts)
-
22
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
-
22
tag = find_tag(opts)
-
22
assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
-
end
-
-
# Identical to +assert_tag+, but asserts that a matching tag does _not_
-
# exist. (See +assert_tag+ for a full discussion of the syntax.)
-
#
-
# # Assert that there is not a "div" containing a "p"
-
# assert_no_tag tag: "div", descendant: { tag: "p" }
-
#
-
# # Assert that an unordered list is empty
-
# assert_no_tag tag: "ul", descendant: { tag: "li" }
-
#
-
# # Assert that there is not a "p" tag with between 1 to 3 "img" tags
-
# # as immediate children
-
# assert_no_tag tag: "p",
-
# children: { count: 1..3, only: { tag: "img" } }
-
1
def assert_no_tag(*opts)
-
18
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
-
18
tag = find_tag(opts)
-
18
assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
-
end
-
-
1
def find_tag(conditions)
-
40
html_document.find(conditions)
-
end
-
-
1
def find_all_tag(conditions)
-
html_document.find_all(conditions)
-
end
-
-
1
def html_document
-
133
xml = @response.content_type =~ /xml$/
-
133
@html_document ||= HTML::Document.new(@response.body, false, xml)
-
end
-
end
-
end
-
end
-
1
require 'stringio'
-
1
require 'uri'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/object/try'
-
1
require 'rack/test'
-
1
require 'minitest/unit'
-
-
1
module ActionDispatch
-
1
module Integration #:nodoc:
-
1
module RequestHelpers
-
# Performs a GET request with the given parameters.
-
#
-
# - +path+: The URI (as a String) on which you want to perform a GET
-
# request.
-
# - +parameters+: The HTTP parameters that you want to pass. This may
-
# be +nil+,
-
# a Hash, or a String that is appropriately encoded
-
# (<tt>application/x-www-form-urlencoded</tt> or
-
# <tt>multipart/form-data</tt>).
-
# - +headers+: Additional headers to pass, as a Hash. The headers will be
-
# merged into the Rack env hash.
-
#
-
# This method returns a Response object, which one can use to
-
# inspect the details of the response. Furthermore, if this method was
-
# called from an ActionDispatch::IntegrationTest object, then that
-
# object's <tt>@response</tt> instance variable will point to the same
-
# response object.
-
#
-
# You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
-
# +#post+, +#patch+, +#put+, +#delete+, and +#head+.
-
1
def get(path, parameters = nil, headers = nil)
-
654
process :get, path, parameters, headers
-
end
-
-
# Performs a POST request with the given parameters. See +#get+ for more
-
# details.
-
1
def post(path, parameters = nil, headers = nil)
-
85
process :post, path, parameters, headers
-
end
-
-
# Performs a PATCH request with the given parameters. See +#get+ for more
-
# details.
-
1
def patch(path, parameters = nil, headers = nil)
-
4
process :patch, path, parameters, headers
-
end
-
-
# Performs a PUT request with the given parameters. See +#get+ for more
-
# details.
-
1
def put(path, parameters = nil, headers = nil)
-
21
process :put, path, parameters, headers
-
end
-
-
# Performs a DELETE request with the given parameters. See +#get+ for
-
# more details.
-
1
def delete(path, parameters = nil, headers = nil)
-
15
process :delete, path, parameters, headers
-
end
-
-
# Performs a HEAD request with the given parameters. See +#get+ for more
-
# details.
-
1
def head(path, parameters = nil, headers = nil)
-
5
process :head, path, parameters, headers
-
end
-
-
# Performs a OPTIONS request with the given parameters. See +#get+ for
-
# more details.
-
1
def options(path, parameters = nil, headers = nil)
-
2
process :options, path, parameters, headers
-
end
-
-
# Performs an XMLHttpRequest request with the given parameters, mirroring
-
# a request from the Prototype library.
-
#
-
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
-
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
-
# string; the headers are a hash.
-
1
def xml_http_request(request_method, path, parameters = nil, headers = nil)
-
10
headers ||= {}
-
10
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
-
10
headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
-
10
process(request_method, path, parameters, headers)
-
end
-
1
alias xhr :xml_http_request
-
-
# Follow a single redirect response. If the last response was not a
-
# redirect, an exception will be raised. Otherwise, the redirect is
-
# performed on the location header.
-
1
def follow_redirect!
-
2
raise "not a redirect! #{status} #{status_message}" unless redirect?
-
1
get(response.location)
-
1
status
-
end
-
-
# Performs a request using the specified method, following any subsequent
-
# redirect. Note that the redirects are followed until the response is
-
# not a redirect--this means you may run into an infinite loop if your
-
# redirect loops back to itself.
-
1
def request_via_redirect(http_method, path, parameters = nil, headers = nil)
-
3
process(http_method, path, parameters, headers)
-
3
follow_redirect! while redirect?
-
3
status
-
end
-
-
# Performs a GET request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def get_via_redirect(path, parameters = nil, headers = nil)
-
1
request_via_redirect(:get, path, parameters, headers)
-
end
-
-
# Performs a POST request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def post_via_redirect(path, parameters = nil, headers = nil)
-
1
request_via_redirect(:post, path, parameters, headers)
-
end
-
-
# Performs a PATCH request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def patch_via_redirect(path, parameters = nil, headers = nil)
-
1
request_via_redirect(:patch, path, parameters, headers)
-
end
-
-
# Performs a PUT request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def put_via_redirect(path, parameters = nil, headers = nil)
-
1
request_via_redirect(:put, path, parameters, headers)
-
end
-
-
# Performs a DELETE request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def delete_via_redirect(path, parameters = nil, headers = nil)
-
1
request_via_redirect(:delete, path, parameters, headers)
-
end
-
end
-
-
# An instance of this class represents a set of requests and responses
-
# performed sequentially by a test process. Because you can instantiate
-
# multiple sessions and run them side-by-side, you can also mimic (to some
-
# limited extent) multiple simultaneous users interacting with your system.
-
#
-
# Typically, you will instantiate a new session using
-
# IntegrationTest#open_session, rather than instantiating
-
# Integration::Session directly.
-
1
class Session
-
1
DEFAULT_HOST = "www.example.com"
-
-
1
include MiniTest::Assertions
-
1
include TestProcess, RequestHelpers, Assertions
-
-
1
%w( status status_message headers body redirect? ).each do |method|
-
5
delegate method, :to => :response, :allow_nil => true
-
end
-
-
1
%w( path ).each do |method|
-
1
delegate method, :to => :request, :allow_nil => true
-
end
-
-
# The hostname used in the last request.
-
1
def host
-
2301
@host || DEFAULT_HOST
-
end
-
1
attr_writer :host
-
-
# The remote_addr used in the last request.
-
1
attr_accessor :remote_addr
-
-
# The Accept header to send.
-
1
attr_accessor :accept
-
-
# A map of the cookies returned by the last response, and which will be
-
# sent with the next request.
-
1
def cookies
-
45
_mock_session.cookie_jar
-
end
-
-
# A reference to the controller instance used by the last request.
-
1
attr_reader :controller
-
-
# A reference to the request instance used by the last request.
-
1
attr_reader :request
-
-
# A reference to the response instance used by the last request.
-
1
attr_reader :response
-
-
# A running counter of the number of requests processed.
-
1
attr_accessor :request_count
-
-
1
include ActionDispatch::Routing::UrlFor
-
-
# Create and initialize a new Session instance.
-
1
def initialize(app)
-
544
super()
-
544
@app = app
-
-
# If the app is a Rails app, make url_helpers available on the session
-
# This makes app.url_for and app.foo_path available in the console
-
544
if app.respond_to?(:routes)
-
460
singleton_class.class_eval do
-
460
include app.routes.url_helpers if app.routes.respond_to?(:url_helpers)
-
460
include app.routes.mounted_helpers if app.routes.respond_to?(:mounted_helpers)
-
end
-
end
-
-
544
reset!
-
end
-
-
1
def url_options
-
@url_options ||= default_url_options.dup.tap do |url_options|
-
247
url_options.reverse_merge!(controller.url_options) if controller
-
-
247
if @app.respond_to?(:routes) && @app.routes.respond_to?(:default_url_options)
-
12
url_options.reverse_merge!(@app.routes.default_url_options)
-
end
-
-
247
url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http")
-
283
end
-
end
-
-
# Resets the instance. This can be used to reset the state information
-
# in an existing session instance, so it can be used from a clean-slate
-
# condition.
-
#
-
# session.reset!
-
1
def reset!
-
544
@https = false
-
544
@controller = @request = @response = nil
-
544
@_mock_session = nil
-
544
@request_count = 0
-
544
@url_options = nil
-
-
544
self.host = DEFAULT_HOST
-
544
self.remote_addr = "127.0.0.1"
-
544
self.accept = "text/xml,application/xml,application/xhtml+xml," +
-
"text/html;q=0.9,text/plain;q=0.8,image/png," +
-
"*/*;q=0.5"
-
-
544
unless defined? @named_routes_configured
-
# the helpers are made protected by default--we make them public for
-
# easier access during testing and troubleshooting.
-
544
@named_routes_configured = true
-
end
-
end
-
-
# Specify whether or not the session should mimic a secure HTTPS request.
-
#
-
# session.https!
-
# session.https!(false)
-
1
def https!(flag = true)
-
27
@https = flag
-
end
-
-
# Return +true+ if the session is mimicking a secure HTTPS request.
-
#
-
# if session.https?
-
# ...
-
# end
-
1
def https?
-
2578
@https
-
end
-
-
# Set the host name to use in the next request.
-
#
-
# session.host! "www.example.com"
-
1
alias :host! :host=
-
-
1
private
-
1
def _mock_session
-
1583
@_mock_session ||= Rack::MockSession.new(@app, host)
-
end
-
-
# Performs the actual request.
-
1
def process(method, path, parameters = nil, rack_env = nil)
-
776
rack_env ||= {}
-
776
if path =~ %r{://}
-
22
location = URI.parse(path)
-
22
https! URI::HTTPS === location if location.scheme
-
22
host! location.host if location.host
-
22
path = location.query ? "#{location.path}?#{location.query}" : location.path
-
end
-
-
776
unless ActionController::Base < ActionController::Testing
-
ActionController::Base.class_eval do
-
include ActionController::Testing
-
end
-
end
-
-
776
hostname, port = host.split(':')
-
-
776
env = {
-
:method => method,
-
:params => parameters,
-
-
"SERVER_NAME" => hostname,
-
775
"SERVER_PORT" => port || (https? ? "443" : "80"),
-
"HTTPS" => https? ? "on" : "off",
-
"rack.url_scheme" => https? ? "https" : "http",
-
-
"REQUEST_URI" => path,
-
"HTTP_HOST" => host,
-
"REMOTE_ADDR" => remote_addr,
-
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
-
"HTTP_ACCEPT" => accept
-
}
-
-
776
session = Rack::Test::Session.new(_mock_session)
-
-
776
env.merge!(rack_env)
-
-
# NOTE: rack-test v0.5 doesn't build a default uri correctly
-
# Make sure requested path is always a full uri
-
776
uri = URI.parse('/')
-
776
uri.scheme ||= env['rack.url_scheme']
-
776
uri.host ||= env['SERVER_NAME']
-
776
uri.port ||= env['SERVER_PORT'].try(:to_i)
-
776
uri += path
-
-
776
session.request(uri.to_s, env)
-
-
762
@request_count += 1
-
762
@request = ActionDispatch::Request.new(session.last_request.env)
-
762
response = _mock_session.last_response
-
762
@response = ActionDispatch::TestResponse.new(response.status, response.headers, response.body)
-
762
@html_document = nil
-
762
@url_options = nil
-
-
762
@controller = session.last_request.env['action_controller.instance']
-
-
762
return response.status
-
end
-
end
-
-
1
module Runner
-
1
include ActionDispatch::Assertions
-
-
1
def app
-
318
@app ||= nil
-
end
-
-
# Reset the current session. This is useful for testing multiple sessions
-
# in a single test case.
-
1
def reset!
-
518
@integration_session = Integration::Session.new(app)
-
end
-
-
1
%w(get post patch put head delete options cookies assigns
-
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
-
13
define_method(method) do |*args|
-
825
reset! unless integration_session
-
# reset the html_document variable, but only for new get/post calls
-
825
@html_document = nil unless method == 'cookies' || method == 'assigns'
-
825
integration_session.__send__(method, *args).tap do
-
811
copy_session_variables!
-
end
-
end
-
end
-
-
# Open a new session instance. If a block is given, the new session is
-
# yielded to the block before being returned.
-
#
-
# session = open_session do |sess|
-
# sess.extend(CustomAssertions)
-
# end
-
#
-
# By default, a single session is automatically created for you, but you
-
# can use this method to open multiple sessions that ought to be tested
-
# simultaneously.
-
1
def open_session(app = nil)
-
4
dup.tap do |session|
-
4
yield session if block_given?
-
end
-
end
-
-
# Copy the instance variables from the current session instance into the
-
# test instance.
-
1
def copy_session_variables! #:nodoc:
-
1239
return unless integration_session
-
1239
%w(controller response request).each do |var|
-
3717
instance_variable_set("@#{var}", @integration_session.__send__(var))
-
end
-
end
-
-
1
def default_url_options
-
5
reset! unless integration_session
-
5
integration_session.default_url_options
-
end
-
-
1
def default_url_options=(options)
-
reset! unless integration_session
-
integration_session.default_url_options = options
-
end
-
-
1
def respond_to?(method, include_private = false)
-
1129
integration_session.respond_to?(method, include_private) || super
-
end
-
-
# Delegate unhandled messages to the current session instance.
-
1
def method_missing(sym, *args, &block)
-
448
reset! unless integration_session
-
448
if integration_session.respond_to?(sym)
-
428
integration_session.__send__(sym, *args, &block).tap do
-
428
copy_session_variables!
-
end
-
else
-
20
super
-
end
-
end
-
-
1
private
-
1
def integration_session
-
5894
@integration_session ||= nil
-
end
-
end
-
end
-
-
# An integration test spans multiple controllers and actions,
-
# tying them all together to ensure they work together as expected. It tests
-
# more completely than either unit or functional tests do, exercising the
-
# entire stack, from the dispatcher to the database.
-
#
-
# At its simplest, you simply extend <tt>IntegrationTest</tt> and write your tests
-
# using the get/post methods:
-
#
-
# require "test_helper"
-
#
-
# class ExampleTest < ActionDispatch::IntegrationTest
-
# fixtures :people
-
#
-
# def test_login
-
# # get the login page
-
# get "/login"
-
# assert_equal 200, status
-
#
-
# # post the login and follow through to the home page
-
# post "/login", username: people(:jamis).username,
-
# password: people(:jamis).password
-
# follow_redirect!
-
# assert_equal 200, status
-
# assert_equal "/home", path
-
# end
-
# end
-
#
-
# However, you can also have multiple session instances open per test, and
-
# even extend those instances with assertions and methods to create a very
-
# powerful testing DSL that is specific for your application. You can even
-
# reference any named routes you happen to have defined.
-
#
-
# require "test_helper"
-
#
-
# class AdvancedTest < ActionDispatch::IntegrationTest
-
# fixtures :people, :rooms
-
#
-
# def test_login_and_speak
-
# jamis, david = login(:jamis), login(:david)
-
# room = rooms(:office)
-
#
-
# jamis.enter(room)
-
# jamis.speak(room, "anybody home?")
-
#
-
# david.enter(room)
-
# david.speak(room, "hello!")
-
# end
-
#
-
# private
-
#
-
# module CustomAssertions
-
# def enter(room)
-
# # reference a named route, for maximum internal consistency!
-
# get(room_url(id: room.id))
-
# assert(...)
-
# ...
-
# end
-
#
-
# def speak(room, message)
-
# xml_http_request "/say/#{room.id}", message: message
-
# assert(...)
-
# ...
-
# end
-
# end
-
#
-
# def login(who)
-
# open_session do |sess|
-
# sess.extend(CustomAssertions)
-
# who = people(who)
-
# sess.post "/login", username: who.username,
-
# password: who.password
-
# assert(...)
-
# end
-
# end
-
# end
-
1
class IntegrationTest < ActiveSupport::TestCase
-
1
include Integration::Runner
-
1
include ActionController::TemplateAssertions
-
1
include ActionDispatch::Routing::UrlFor
-
-
# Use AD::IntegrationTest for acceptance tests
-
1
register_spec_type(/(Acceptance|Integration) ?Test\z/i, self)
-
-
1
@@app = nil
-
-
1
def self.app
-
345
if !@@app && !ActionDispatch.test_app
-
ActiveSupport::Deprecation.warn "Rails application fallback is deprecated and no longer works, please set ActionDispatch.test_app"
-
end
-
-
345
@@app || ActionDispatch.test_app
-
end
-
-
1
def self.app=(app)
-
293
@@app = app
-
end
-
-
1
def app
-
318
super || self.class.app
-
end
-
-
1
def url_options
-
271
reset! unless integration_session
-
271
integration_session.url_options
-
end
-
end
-
end
-
1
require 'action_dispatch/middleware/cookies'
-
1
require 'action_dispatch/middleware/flash'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActionDispatch
-
1
module TestProcess
-
1
def assigns(key = nil)
-
119
assigns = {}.with_indifferent_access
-
426
@controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)}
-
119
key.nil? ? assigns : assigns[key]
-
end
-
-
1
def session
-
31
@request.session
-
end
-
-
1
def flash
-
8
@request.flash
-
end
-
-
1
def cookies
-
48
@request.cookie_jar
-
end
-
-
1
def redirect_to_url
-
34
@response.redirect_url
-
end
-
-
# Shortcut for <tt>Rack::Test::UploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>:
-
#
-
# post :change_avatar, avatar: fixture_file_upload('/files/spongebob.png', 'image/png')
-
#
-
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
-
# This will not affect other platforms:
-
#
-
# post :change_avatar, avatar: fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
-
1
def fixture_file_upload(path, mime_type = nil, binary = false)
-
6
fixture_path = self.class.fixture_path if self.class.respond_to?(:fixture_path)
-
6
Rack::Test::UploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'rack/utils'
-
-
1
module ActionDispatch
-
1
class TestRequest < Request
-
1
DEFAULT_ENV = Rack::MockRequest.env_for('/')
-
-
1
def self.new(env = {})
-
4278
super
-
end
-
-
1
def initialize(env = {})
-
4278
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
-
4278
super(default_env.merge(env))
-
-
4278
self.host = 'test.host'
-
4278
self.remote_addr = '0.0.0.0'
-
4278
self.user_agent = 'Rails Testing'
-
end
-
-
1
def request_method=(method)
-
1804
@env['REQUEST_METHOD'] = method.to_s.upcase
-
end
-
-
1
def host=(host)
-
4633
@env['HTTP_HOST'] = host
-
end
-
-
1
def port=(number)
-
8
@env['SERVER_PORT'] = number.to_i
-
end
-
-
1
def request_uri=(uri)
-
@env['REQUEST_URI'] = uri
-
end
-
-
1
def path=(path)
-
1806
@env['PATH_INFO'] = path
-
end
-
-
1
def action=(action_name)
-
3
path_parameters["action"] = action_name.to_s
-
end
-
-
1
def if_modified_since=(last_modified)
-
8
@env['HTTP_IF_MODIFIED_SINCE'] = last_modified
-
end
-
-
1
def if_none_match=(etag)
-
4
@env['HTTP_IF_NONE_MATCH'] = etag
-
end
-
-
1
def remote_addr=(addr)
-
4279
@env['REMOTE_ADDR'] = addr
-
end
-
-
1
def user_agent=(user_agent)
-
4278
@env['HTTP_USER_AGENT'] = user_agent
-
end
-
-
1
def accept=(mime_types)
-
83
@env.delete('action_dispatch.request.accepts')
-
166
@env['HTTP_ACCEPT'] = Array(mime_types).collect { |mime_type| mime_type.to_s }.join(",")
-
end
-
-
1
alias :rack_cookies :cookies
-
-
1
def cookies
-
2134
@cookies ||= {}.with_indifferent_access
-
end
-
-
1
private
-
-
1
def default_env
-
3
DEFAULT_ENV
-
end
-
end
-
end
-
1
module ActionDispatch
-
# Integration test methods such as ActionDispatch::Integration::Session#get
-
# and ActionDispatch::Integration::Session#post return objects of class
-
# TestResponse, which represent the HTTP response results of the requested
-
# controller actions.
-
#
-
# See Response for more information on controller response objects.
-
1
class TestResponse < Response
-
1
def self.from_response(response)
-
new.tap do |resp|
-
resp.status = response.status
-
resp.headers = response.headers
-
resp.body = response.body
-
end
-
end
-
-
# Was the response successful?
-
1
alias_method :success?, :successful?
-
-
# Was the URL not found?
-
1
alias_method :missing?, :not_found?
-
-
# Were we redirected?
-
1
alias_method :redirect?, :redirection?
-
-
# Was there a server-side error?
-
1
alias_method :error?, :server_error?
-
end
-
end
-
#--
-
# Copyright (c) 2004-2012 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
1
require 'action_pack/version'
-
1
module ActionPack
-
1
module VERSION #:nodoc:
-
1
MAJOR = 4
-
1
MINOR = 0
-
1
TINY = 0
-
1
PRE = "beta"
-
-
1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
-
end
-
end
-
#--
-
# Copyright (c) 2004-2012 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
1
require 'active_support'
-
1
require 'active_support/rails'
-
1
require 'action_pack'
-
-
1
module ActionView
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :Base
-
1
autoload :Context
-
1
autoload :CompiledTemplates, "action_view/context"
-
1
autoload :Digestor
-
1
autoload :Helpers
-
1
autoload :LookupContext
-
1
autoload :PathSet
-
1
autoload :RecordIdentifier
-
1
autoload :RoutingUrlFor
-
1
autoload :Template
-
-
1
autoload_under "renderer" do
-
1
autoload :Renderer
-
1
autoload :AbstractRenderer
-
1
autoload :PartialRenderer
-
1
autoload :TemplateRenderer
-
1
autoload :StreamingTemplateRenderer
-
end
-
-
1
autoload_at "action_view/template/resolver" do
-
1
autoload :Resolver
-
1
autoload :PathResolver
-
1
autoload :FileSystemResolver
-
1
autoload :OptimizedFileSystemResolver
-
1
autoload :FallbackFileSystemResolver
-
end
-
-
1
autoload_at "action_view/buffers" do
-
1
autoload :OutputBuffer
-
1
autoload :StreamingBuffer
-
end
-
-
1
autoload_at "action_view/flows" do
-
1
autoload :OutputFlow
-
1
autoload :StreamingFlow
-
end
-
-
1
autoload_at "action_view/template/error" do
-
1
autoload :MissingTemplate
-
1
autoload :ActionViewError
-
1
autoload :EncodingError
-
1
autoload :MissingRequestError
-
1
autoload :TemplateError
-
1
autoload :WrongEncodingError
-
end
-
end
-
-
1
autoload :TestCase
-
-
1
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
-
-
1
def self.eager_load!
-
super
-
ActionView::Template.eager_load!
-
end
-
end
-
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
ActiveSupport.on_load(:i18n) do
-
1
I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
-
end
-
1
require 'active_support/core_ext/module/attr_internal'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/ordered_options'
-
1
require 'action_view/log_subscriber'
-
-
1
module ActionView #:nodoc:
-
# = Action View Base
-
#
-
# Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERb
-
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used.
-
#
-
# == ERB
-
#
-
# You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
-
# following loop for names:
-
#
-
# <b>Names of all the people</b>
-
# <% @people.each do |person| %>
-
# Name: <%= person.name %><br/>
-
# <% end %>
-
#
-
# The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
-
# is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
-
#
-
# <%# WRONG %>
-
# Hi, Mr. <% puts "Frodo" %>
-
#
-
# If you absolutely must write from within a function use +concat+.
-
#
-
# <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
-
#
-
# === Using sub templates
-
#
-
# Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
-
# classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
-
#
-
# <%= render "shared/header" %>
-
# Something really specific and terrific
-
# <%= render "shared/footer" %>
-
#
-
# As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
-
# result of the rendering. The output embedding writes it to the current template.
-
#
-
# But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
-
# variables defined using the regular embedding tags. Like this:
-
#
-
# <% @page_title = "A Wonderful Hello" %>
-
# <%= render "shared/header" %>
-
#
-
# Now the header can pick up on the <tt>@page_title</tt> variable and use it for outputting a title tag:
-
#
-
# <title><%= @page_title %></title>
-
#
-
# === Passing local variables to sub templates
-
#
-
# You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
-
#
-
# <%= render "shared/header", { headline: "Welcome", person: person } %>
-
#
-
# These can now be accessed in <tt>shared/header</tt> with:
-
#
-
# Headline: <%= headline %>
-
# First name: <%= person.first_name %>
-
#
-
# If you need to find out whether a certain local variable has been assigned a value in a particular render call,
-
# you need to use the following pattern:
-
#
-
# <% if local_assigns.has_key? :headline %>
-
# Headline: <%= headline %>
-
# <% end %>
-
#
-
# Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction.
-
#
-
# === Template caching
-
#
-
# By default, Rails will compile each template to a method in order to render it. When you alter a template,
-
# Rails will check the file's modification time and recompile it in development mode.
-
#
-
# == Builder
-
#
-
# Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object
-
# named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
-
#
-
# Here are some basic examples:
-
#
-
# xml.em("emphasized") # => <em>emphasized</em>
-
# xml.em { xml.b("emph & bold") } # => <em><b>emph & bold</b></em>
-
# xml.a("A Link", "href" => "http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
-
# xml.target("name" => "compile", "option" => "fast") # => <target option="fast" name="compile"\>
-
# # NOTE: order of attributes is not specified.
-
#
-
# Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
-
#
-
# xml.div do
-
# xml.h1(@person.name)
-
# xml.p(@person.bio)
-
# end
-
#
-
# would produce something like:
-
#
-
# <div>
-
# <h1>David Heinemeier Hansson</h1>
-
# <p>A product of Danish Design during the Winter of '79...</p>
-
# </div>
-
#
-
# A full-length RSS example actually used on Basecamp:
-
#
-
# xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
-
# xml.channel do
-
# xml.title(@feed_title)
-
# xml.link(@url)
-
# xml.description "Basecamp: Recent items"
-
# xml.language "en-us"
-
# xml.ttl "40"
-
#
-
# @recent_items.each do |item|
-
# xml.item do
-
# xml.title(item_title(item))
-
# xml.description(item_description(item)) if item_description(item)
-
# xml.pubDate(item_pubDate(item))
-
# xml.guid(@person.firm.account.url + @recent_items.url(item))
-
# xml.link(@person.firm.account.url + @recent_items.url(item))
-
#
-
# xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
-
# end
-
# end
-
# end
-
# end
-
#
-
# More builder documentation can be found at http://builder.rubyforge.org.
-
1
class Base
-
1
include Helpers, ::ERB::Util, Context
-
-
# Specify the proc used to decorate input tags that refer to attributes with errors.
-
1
cattr_accessor :field_error_proc
-
15
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
-
-
# How to complete the streaming when an exception occurs.
-
# This is our best guess: first try to close the attribute, then the tag.
-
1
cattr_accessor :streaming_completion_on_exception
-
1
@@streaming_completion_on_exception = %("><script>window.location = "/500.html"</script></html>)
-
-
# Specify whether rendering within namespaced controllers should prefix
-
# the partial paths for ActiveModel objects with the namespace.
-
# (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb)
-
1
cattr_accessor :prefix_partial_path_with_controller_namespace
-
1
@@prefix_partial_path_with_controller_namespace = true
-
-
# Specify default_formats that can be rendered.
-
1
cattr_accessor :default_formats
-
-
1
class_attribute :_routes
-
1
class_attribute :logger
-
-
1
class << self
-
1
delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
-
-
1
def cache_template_loading
-
ActionView::Resolver.caching?
-
end
-
-
1
def cache_template_loading=(value)
-
ActionView::Resolver.caching = value
-
end
-
-
1
def xss_safe? #:nodoc:
-
true
-
end
-
end
-
-
1
attr_accessor :view_renderer
-
1
attr_internal :config, :assigns
-
-
1
delegate :lookup_context, :to => :view_renderer
-
1
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
-
-
1
def assign(new_assigns) # :nodoc:
-
3632
@_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
-
end
-
-
1
def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
-
1577
@_config = ActiveSupport::InheritableOptions.new
-
-
1577
if context.is_a?(ActionView::Renderer)
-
1308
@view_renderer = context
-
else
-
269
lookup_context = context.is_a?(ActionView::LookupContext) ?
-
context : ActionView::LookupContext.new(context)
-
269
lookup_context.formats = formats if formats
-
269
lookup_context.prefixes = controller._prefixes if controller
-
269
@view_renderer = ActionView::Renderer.new(lookup_context)
-
end
-
-
1577
assign(assigns)
-
1577
assign_controller(controller)
-
1577
_prepare_context
-
end
-
-
1
ActiveSupport.run_load_hooks(:action_view, self)
-
end
-
end
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView
-
1
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
-
1
def initialize(*)
-
1494
super
-
1494
encode!
-
end
-
-
1
def <<(value)
-
1511
super(value.to_s)
-
end
-
1
alias :append= :<<
-
1
alias :safe_append= :safe_concat
-
end
-
-
1
class StreamingBuffer #:nodoc:
-
1
def initialize(block)
-
15
@block = block
-
end
-
-
1
def <<(value)
-
26
value = value.to_s
-
26
value = ERB::Util.h(value) unless value.html_safe?
-
26
@block.call(value)
-
end
-
1
alias :concat :<<
-
1
alias :append= :<<
-
-
1
def safe_concat(value)
-
18
@block.call(value.to_s)
-
end
-
1
alias :safe_append= :safe_concat
-
-
1
def html_safe?
-
true
-
end
-
-
1
def html_safe
-
self
-
end
-
end
-
end
-
1
module ActionView
-
1
module CompiledTemplates #:nodoc:
-
# holds compiled template code
-
end
-
-
# = Action View Context
-
#
-
# Action View contexts are supplied to Action Controller to render a template.
-
# The default Action View context is ActionView::Base.
-
#
-
# In order to work with ActionController, a Context must just include this module.
-
# The initialization of the variables used by the context (@output_buffer, @view_flow,
-
# and @virtual_path) is responsibility of the object that includes this module
-
# (although you can call _prepare_context defined below).
-
1
module Context
-
1
include CompiledTemplates
-
1
attr_accessor :output_buffer, :view_flow
-
-
# Prepares the context by setting the appropriate instance variables.
-
# :api: plugin
-
1
def _prepare_context
-
1648
@view_flow = OutputFlow.new
-
1648
@output_buffer = nil
-
1648
@virtual_path = nil
-
end
-
-
# Encapsulates the interaction with the view flow so it
-
# returns the correct buffer on +yield+. This is usually
-
# overwritten by helpers to add more behavior.
-
# :api: plugin
-
1
def _layout_for(name=nil)
-
158
name ||= :layout
-
158
view_flow.get(name).html_safe
-
end
-
end
-
end
-
1
require 'mutex_m'
-
-
1
module ActionView
-
1
class Digestor
-
1
EXPLICIT_DEPENDENCY = /# Template Dependency: ([^ ]+)/
-
-
# Matches:
-
# render partial: "comments/comment", collection: commentable.comments
-
# render "comments/comments"
-
# render 'comments/comments'
-
# render('comments/comments')
-
#
-
# render(@topic) => render("topics/topic")
-
# render(topics) => render("topics/topic")
-
# render(message.topics) => render("topics/topic")
-
1
RENDER_DEPENDENCY = /
-
render\s* # render, followed by optional whitespace
-
\(? # start an optional parenthesis for the render call
-
(partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture
-
([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture
-
/x
-
-
1
cattr_reader(:cache)
-
1
@@cache = Hash.new.extend Mutex_m
-
-
1
def self.digest(name, format, finder, options = {})
-
42
cache.synchronize do
-
42
unsafe_digest name, format, finder, options
-
end
-
end
-
-
###
-
# This method is NOT thread safe. DO NOT CALL IT DIRECTLY, instead call
-
# Digestor.digest
-
1
def self.unsafe_digest(name, format, finder, options = {}) # :nodoc:
-
199
key = "#{name}.#{format}"
-
-
199
cache.fetch(key) do
-
194
klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor
-
194
cache[key] = klass.new(name, format, finder).digest
-
end
-
end
-
-
1
attr_reader :name, :format, :finder
-
-
1
def initialize(name, format, finder)
-
194
@name, @format, @finder = name, format, finder
-
end
-
-
1
def digest
-
194
Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
-
179
logger.try :info, "Cache digest for #{name}.#{format}: #{digest}"
-
end
-
rescue ActionView::MissingTemplate
-
15
logger.try :error, "Couldn't find template for digesting: #{name}.#{format}"
-
15
''
-
end
-
-
1
def dependencies
-
179
render_dependencies + explicit_dependencies
-
rescue ActionView::MissingTemplate
-
[] # File doesn't exist, so no dependencies
-
end
-
-
1
def nested_dependencies
-
dependencies.collect do |dependency|
-
dependencies = PartialDigestor.new(dependency, format, finder).nested_dependencies
-
dependencies.any? ? { dependency => dependencies } : dependency
-
end
-
end
-
-
1
private
-
-
1
def logger
-
194
ActionView::Base.logger
-
end
-
-
1
def logical_name
-
194
name.gsub(%r|/_|, "/")
-
end
-
-
1
def directory
-
50
name.split("/")[0..-2].join("/")
-
end
-
-
1
def partial?
-
36
false
-
end
-
-
1
def source
-
552
@source ||= finder.find(logical_name, [], partial?, formats: [ format ]).source
-
end
-
-
1
def dependency_digest
-
179
dependencies.collect do |template_name|
-
157
Digestor.unsafe_digest(template_name, format, finder, partial: true)
-
end.join("-")
-
end
-
-
1
def render_dependencies
-
179
source.scan(RENDER_DEPENDENCY).
-
collect(&:second).uniq.
-
-
# render(@topic) => render("topics/topic")
-
# render(topics) => render("topics/topic")
-
# render(message.topics) => render("topics/topic")
-
185
collect { |name| name.sub(/\A@?([a-z]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }.
-
-
# render("headline") => render("message/headline")
-
143
collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }.
-
-
# replace quotes from string renders
-
143
collect { |name| name.gsub(/["']/, "") }
-
end
-
-
1
def explicit_dependencies
-
179
source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
-
end
-
end
-
-
1
class PartialDigestor < Digestor # :nodoc:
-
1
def partial?
-
158
true
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView
-
1
class OutputFlow #:nodoc:
-
1
attr_reader :content
-
-
1
def initialize
-
1721
@content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
-
end
-
-
# Called by _layout_for to read stored values.
-
1
def get(key)
-
185
@content[key]
-
end
-
-
# Called by each renderer object to set the layout contents.
-
1
def set(key, value)
-
132
@content[key] = value
-
end
-
-
# Called by content_for
-
1
def append(key, value)
-
41
@content[key] << value
-
end
-
1
alias_method :append!, :append
-
-
end
-
-
1
class StreamingFlow < OutputFlow #:nodoc:
-
1
def initialize(view, fiber)
-
15
@view = view
-
15
@parent = nil
-
15
@child = view.output_buffer
-
15
@content = view.view_flow.content
-
15
@fiber = fiber
-
15
@root = Fiber.current.object_id
-
end
-
-
# Try to get an stored content. If the content
-
# is not available and we are inside the layout
-
# fiber, we set that we are waiting for the given
-
# key and yield.
-
1
def get(key)
-
28
return super if @content.key?(key)
-
-
20
if inside_fiber?
-
19
view = @view
-
-
19
begin
-
19
@waiting_for = key
-
19
view.output_buffer, @parent = @child, view.output_buffer
-
19
Fiber.yield
-
ensure
-
17
@waiting_for = nil
-
17
view.output_buffer, @child = @parent, view.output_buffer
-
end
-
end
-
-
18
super
-
end
-
-
# Appends the contents for the given key. This is called
-
# by provides and resumes back to the fiber if it is
-
# the key it is waiting for.
-
1
def append!(key, value)
-
3
super
-
3
@fiber.resume if @waiting_for == key
-
end
-
-
1
private
-
-
1
def inside_fiber?
-
20
Fiber.current.object_id != @root
-
end
-
end
-
end
-
1
module ActionView #:nodoc:
-
1
module Helpers #:nodoc:
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :ActiveModelHelper
-
1
autoload :AssetTagHelper
-
1
autoload :AssetUrlHelper
-
1
autoload :AtomFeedHelper
-
1
autoload :BenchmarkHelper
-
1
autoload :CacheHelper
-
1
autoload :CaptureHelper
-
1
autoload :ControllerHelper
-
1
autoload :CsrfHelper
-
1
autoload :DateHelper
-
1
autoload :DebugHelper
-
1
autoload :FormHelper
-
1
autoload :FormOptionsHelper
-
1
autoload :FormTagHelper
-
1
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
-
1
autoload :NumberHelper
-
1
autoload :OutputSafetyHelper
-
1
autoload :RecordTagHelper
-
1
autoload :RenderingHelper
-
1
autoload :SanitizeHelper
-
1
autoload :TagHelper
-
1
autoload :TextHelper
-
1
autoload :TranslationHelper
-
1
autoload :UrlHelper
-
-
1
extend ActiveSupport::Concern
-
-
1
include ActiveModelHelper
-
1
include AssetTagHelper
-
1
include AssetUrlHelper
-
1
include AtomFeedHelper
-
1
include BenchmarkHelper
-
1
include CacheHelper
-
1
include CaptureHelper
-
1
include ControllerHelper
-
1
include CsrfHelper
-
1
include DateHelper
-
1
include DebugHelper
-
1
include FormHelper
-
1
include FormOptionsHelper
-
1
include FormTagHelper
-
1
include JavaScriptHelper
-
1
include NumberHelper
-
1
include OutputSafetyHelper
-
1
include RecordTagHelper
-
1
include RenderingHelper
-
1
include SanitizeHelper
-
1
include TagHelper
-
1
include TextHelper
-
1
include TranslationHelper
-
1
include UrlHelper
-
end
-
end
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/enumerable'
-
-
1
module ActionView
-
# = Active Model Helpers
-
1
module Helpers
-
1
module ActiveModelHelper
-
end
-
-
1
module ActiveModelInstanceTag
-
1
def object
-
@active_model_object ||= begin
-
object = super
-
object.respond_to?(:to_model) ? object.to_model : object
-
end
-
end
-
-
1
def content_tag(*)
-
261
error_wrapping(super)
-
end
-
-
1
def tag(type, options, *)
-
460
tag_generate_errors?(options) ? error_wrapping(super) : super
-
end
-
-
1
def error_wrapping(html_tag)
-
692
if object_has_errors?
-
15
Base.field_error_proc.call(html_tag, self)
-
else
-
677
html_tag
-
end
-
end
-
-
1
def error_message
-
304
object.errors[@method_name]
-
end
-
-
1
private
-
-
1
def object_has_errors?
-
692
object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
-
end
-
-
1
def tag_generate_errors?(options)
-
460
options['type'] != 'hidden'
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'action_view/helpers/asset_url_helper'
-
1
require 'action_view/helpers/tag_helper'
-
-
1
module ActionView
-
# = Action View Asset Tag Helpers
-
1
module Helpers #:nodoc:
-
# This module provides methods for generating HTML that links views to assets such
-
# as images, javascripts, stylesheets, and feeds. These methods do not verify
-
# the assets exist before linking to them:
-
#
-
# image_tag("rails.png")
-
# # => <img alt="Rails" src="/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
-
#
-
1
module AssetTagHelper
-
1
extend ActiveSupport::Concern
-
-
1
include AssetUrlHelper
-
1
include TagHelper
-
-
# Returns an HTML script tag for each of the +sources+ provided.
-
#
-
# Sources may be paths to JavaScript files. Relative paths are assumed to be relative
-
# to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document
-
# root. Relative paths are idiomatic, use absolute paths only when needed.
-
#
-
# When passing paths, the ".js" extension is optional.
-
#
-
# You can modify the HTML attributes of the script tag by passing a hash as the
-
# last argument.
-
#
-
# javascript_include_tag "xmlhr"
-
# # => <script src="/assets/xmlhr.js?1284139606"></script>
-
#
-
# javascript_include_tag "xmlhr.js"
-
# # => <script src="/assets/xmlhr.js?1284139606"></script>
-
#
-
# javascript_include_tag "common.javascript", "/elsewhere/cools"
-
# # => <script src="/assets/common.javascript?1284139606"></script>
-
# # <script src="/elsewhere/cools.js?1423139606"></script>
-
#
-
# javascript_include_tag "http://www.example.com/xmlhr"
-
# # => <script src="http://www.example.com/xmlhr"></script>
-
#
-
# javascript_include_tag "http://www.example.com/xmlhr.js"
-
# # => <script src="http://www.example.com/xmlhr.js"></script>
-
#
-
1
def javascript_include_tag(*sources)
-
9
options = sources.extract_options!.stringify_keys
-
9
sources.uniq.map { |source|
-
9
tag_options = {
-
"src" => path_to_javascript(source)
-
}.merge(options)
-
9
content_tag(:script, "", tag_options)
-
}.join("\n").html_safe
-
end
-
-
# Returns a stylesheet link tag for the sources specified as arguments. If
-
# you don't specify an extension, <tt>.css</tt> will be appended automatically.
-
# You can modify the link attributes by passing a hash as the last argument.
-
# For historical reasons, the 'media' attribute will always be present and defaults
-
# to "screen", so you must explicitely set it to "all" for the stylesheet(s) to
-
# apply to all media types.
-
#
-
# stylesheet_link_tag "style"
-
# # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "style.css"
-
# # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "http://www.example.com/style.css"
-
# # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "style", media: "all"
-
# # => <link href="/assets/style.css" media="all" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "style", media: "print"
-
# # => <link href="/assets/style.css" media="print" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "random.styles", "/css/stylish"
-
# # => <link href="/assets/random.styles" media="screen" rel="stylesheet" />
-
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
-
#
-
1
def stylesheet_link_tag(*sources)
-
16
options = sources.extract_options!.stringify_keys
-
16
sources.uniq.map { |source|
-
18
tag_options = {
-
"rel" => "stylesheet",
-
"media" => "screen",
-
"href" => path_to_stylesheet(source)
-
}.merge(options)
-
18
tag(:link, tag_options)
-
}.join("\n").html_safe
-
end
-
-
# Returns a link tag that browsers and news readers can use to auto-detect
-
# an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
-
# <tt>:atom</tt>. Control the link options in url_for format using the
-
# +url_options+. You can modify the LINK tag itself in +tag_options+.
-
#
-
# ==== Options
-
# * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
-
# * <tt>:type</tt> - Override the auto-generated mime type
-
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
-
#
-
# ==== Examples
-
# auto_discovery_link_tag
-
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
-
# auto_discovery_link_tag(:atom)
-
# # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
-
# auto_discovery_link_tag(:rss, {action: "feed"})
-
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
-
# auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"})
-
# # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
-
# auto_discovery_link_tag(:rss, {controller: "news", action: "feed"})
-
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
-
# auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"})
-
# # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
-
1
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
-
16
if !(type == :rss || type == :atom) && tag_options[:type].blank?
-
1
message = "You have passed type other than :rss or :atom to auto_discovery_link_tag and haven't supplied " +
-
"the :type option key. This behavior is deprecated and will be remove in Rails 4.1. You should pass " +
-
":type option explicitly if you want to use other types, for example: " +
-
"auto_discovery_link_tag(:xml, '/feed.xml', :type => 'application/xml')"
-
1
ActiveSupport::Deprecation.warn message
-
end
-
-
tag(
-
16
"link",
-
"rel" => tag_options[:rel] || "alternate",
-
"type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
-
"title" => tag_options[:title] || type.to_s.upcase,
-
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
-
)
-
end
-
-
# <%= favicon_link_tag %>
-
#
-
# generates
-
#
-
# <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
-
#
-
# You may specify a different file in the first argument:
-
#
-
# <%= favicon_link_tag '/myicon.ico' %>
-
#
-
# That's passed to +path_to_image+ as is, so it gives
-
#
-
# <link href="/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
-
#
-
# The helper accepts an additional options hash where you can override "rel" and "type".
-
#
-
# For example, Mobile Safari looks for a different LINK tag, pointing to an image that
-
# will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
-
# The following call would generate such a tag:
-
#
-
# <%= favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' %>
-
1
def favicon_link_tag(source='favicon.ico', options={})
-
5
tag('link', {
-
:rel => 'shortcut icon',
-
:type => 'image/vnd.microsoft.icon',
-
:href => path_to_image(source)
-
}.merge(options.symbolize_keys))
-
end
-
-
# Returns an html image tag for the +source+. The +source+ can be a full
-
# path or a file.
-
#
-
# ==== Options
-
# You can add HTML attributes using the +options+. The +options+ supports
-
# three additional keys for convenience and conformance:
-
#
-
# * <tt>:alt</tt> - If no alt text is given, the file name part of the
-
# +source+ is used (capitalized and without the extension)
-
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
-
# width="30" and height="45", and "50" becomes width="50" and height="50".
-
# <tt>:size</tt> will be ignored if the value is not in the correct format.
-
#
-
# image_tag("icon")
-
# # => <img alt="Icon" src="/assets/icon" />
-
# image_tag("icon.png")
-
# # => <img alt="Icon" src="/assets/icon.png" />
-
# image_tag("icon.png", size: "16x10", alt: "Edit Entry")
-
# # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
-
# image_tag("/icons/icon.gif", size: "16")
-
# # => <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
-
# image_tag("/icons/icon.gif", height: '32', width: '32')
-
# # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
-
# image_tag("/icons/icon.gif", class: "menu_icon")
-
# # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
-
1
def image_tag(source, options={})
-
19
options = options.symbolize_keys
-
-
19
src = options[:src] = path_to_image(source)
-
-
19
unless src =~ /^(?:cid|data):/ || src.blank?
-
28
options[:alt] = options.fetch(:alt){ image_alt(src) }
-
end
-
-
19
if size = options.delete(:size)
-
6
options[:width], options[:height] = size.split("x") if size =~ %r{\A\d+x\d+\z}
-
6
options[:width] = options[:height] = size if size =~ %r{\A\d+\z}
-
end
-
-
19
tag("img", options)
-
end
-
-
1
def image_alt(src)
-
25
File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize
-
end
-
-
# Returns an html video tag for the +sources+. If +sources+ is a string,
-
# a single video tag will be returned. If +sources+ is an array, a video
-
# tag with nested source tags for each source will be returned. The
-
# +sources+ can be full paths or files that exists in your public videos
-
# directory.
-
#
-
# ==== Options
-
# You can add HTML attributes using the +options+. The +options+ supports
-
# two additional keys for convenience and conformance:
-
#
-
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
-
# before the video loads. The path is calculated like the +src+ of +image_tag+.
-
# * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
-
# width="30" and height="45". <tt>:size</tt> will be ignored if the
-
# value is not in the correct format.
-
#
-
# video_tag("trailer")
-
# # => <video src="/videos/trailer" />
-
# video_tag("trailer.ogg")
-
# # => <video src="/videos/trailer.ogg" />
-
# video_tag("trailer.ogg", controls: true, autobuffer: true)
-
# # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
-
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
-
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
-
# video_tag("/trailers/hd.avi", size: "16x16")
-
# # => <video src="/trailers/hd.avi" width="16" height="16" />
-
# video_tag("/trailers/hd.avi", height: '32', width: '32')
-
# # => <video height="32" src="/trailers/hd.avi" width="32" />
-
# video_tag("trailer.ogg", "trailer.flv")
-
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
-
# video_tag(["trailer.ogg", "trailer.flv"])
-
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
-
# video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
-
# # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
-
1
def video_tag(*sources)
-
15
multiple_sources_tag('video', sources) do |options|
-
15
options[:poster] = path_to_image(options[:poster]) if options[:poster]
-
-
15
if size = options.delete(:size)
-
6
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
-
end
-
end
-
end
-
-
# Returns an html audio tag for the +source+.
-
# The +source+ can be full path or file that exists in
-
# your public audios directory.
-
#
-
# audio_tag("sound") # =>
-
# <audio src="/audios/sound" />
-
# audio_tag("sound.wav") # =>
-
# <audio src="/audios/sound.wav" />
-
# audio_tag("sound.wav", autoplay: true, controls: true) # =>
-
# <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
-
# audio_tag("sound.wav", "sound.mid") # =>
-
# <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
-
1
def audio_tag(*sources)
-
8
multiple_sources_tag('audio', sources)
-
end
-
-
1
private
-
1
def multiple_sources_tag(type, sources)
-
23
options = sources.extract_options!.symbolize_keys
-
23
sources.flatten!
-
-
23
yield options if block_given?
-
-
23
if sources.size > 1
-
6
content_tag(type, options) do
-
18
safe_join sources.map { |source| tag("source", :src => send("path_to_#{type}", source)) }
-
end
-
else
-
17
options[:src] = send("path_to_#{type}", sources.first)
-
17
content_tag(type, nil, options)
-
end
-
end
-
end
-
end
-
end
-
1
require 'zlib'
-
-
1
module ActionView
-
# = Action View Asset URL Helpers
-
1
module Helpers #:nodoc:
-
# This module provides methods for generating asset paths and
-
# urls.
-
#
-
# image_path("rails.png")
-
# # => "/assets/rails.png"
-
#
-
# image_url("rails.png")
-
# # => "http://www.example.com/assets/rails.png"
-
#
-
# === Using asset hosts
-
#
-
# By default, Rails links to these assets on the current host in the public
-
# folder, but you can direct Rails to link to assets from a dedicated asset
-
# server by setting <tt>ActionController::Base.asset_host</tt> in the application
-
# configuration, typically in <tt>config/environments/production.rb</tt>.
-
# For example, you'd define <tt>assets.example.com</tt> to be your asset
-
# host this way, inside the <tt>configure</tt> block of your environment-specific
-
# configuration files or <tt>config/application.rb</tt>:
-
#
-
# config.action_controller.asset_host = "assets.example.com"
-
#
-
# Helpers take that into account:
-
#
-
# image_tag("rails.png")
-
# # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
-
#
-
# Browsers typically open at most two simultaneous connections to a single
-
# host, which means your assets often have to wait for other assets to finish
-
# downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
-
# +asset_host+. For example, "assets%d.example.com". If that wildcard is
-
# present Rails distributes asset requests among the corresponding four hosts
-
# "assets0.example.com", ..., "assets3.example.com". With this trick browsers
-
# will open eight simultaneous connections rather than two.
-
#
-
# image_tag("rails.png")
-
# # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
-
#
-
# To do this, you can either setup four actual hosts, or you can use wildcard
-
# DNS to CNAME the wildcard to a single asset host. You can read more about
-
# setting up your DNS CNAME records from your ISP.
-
#
-
# Note: This is purely a browser performance optimization and is not meant
-
# for server load balancing. See http://www.die.net/musings/page_load_time/
-
# for background.
-
#
-
# Alternatively, you can exert more control over the asset host by setting
-
# +asset_host+ to a proc like this:
-
#
-
# ActionController::Base.asset_host = Proc.new { |source|
-
# "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
-
# }
-
# image_tag("rails.png")
-
# # => <img alt="Rails" src="http://assets1.example.com/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
-
#
-
# The example above generates "http://assets1.example.com" and
-
# "http://assets2.example.com". This option is useful for example if
-
# you need fewer/more than four hosts, custom host names, etc.
-
#
-
# As you see the proc takes a +source+ parameter. That's a string with the
-
# absolute path of the asset, for example "/assets/rails.png".
-
#
-
# ActionController::Base.asset_host = Proc.new { |source|
-
# if source.ends_with?('.css')
-
# "http://stylesheets.example.com"
-
# else
-
# "http://assets.example.com"
-
# end
-
# }
-
# image_tag("rails.png")
-
# # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" />
-
#
-
# Alternatively you may ask for a second parameter +request+. That one is
-
# particularly useful for serving assets from an SSL-protected page. The
-
# example proc below disables asset hosting for HTTPS connections, while
-
# still sending assets for plain HTTP requests from asset hosts. If you don't
-
# have SSL certificates for each of the asset hosts this technique allows you
-
# to avoid warnings in the client about mixed media.
-
#
-
# config.action_controller.asset_host = Proc.new { |source, request|
-
# if request.ssl?
-
# "#{request.protocol}#{request.host_with_port}"
-
# else
-
# "#{request.protocol}assets.example.com"
-
# end
-
# }
-
#
-
# You can also implement a custom asset host object that responds to +call+
-
# and takes either one or two parameters just like the proc.
-
#
-
# config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
-
# "http://asset%d.example.com", "https://asset1.example.com"
-
# )
-
#
-
1
module AssetUrlHelper
-
1
URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
-
-
# Computes the path to asset in public directory. If :type
-
# options is set, a file extension will be appended and scoped
-
# to the corresponding public directory.
-
#
-
# All other asset *_path helpers delegate through this method.
-
#
-
# asset_path "application.js" # => /application.js
-
# asset_path "application", type: :javascript # => /javascripts/application.js
-
# asset_path "application", type: :stylesheet # => /stylesheets/application.css
-
# asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
-
1
def asset_path(source, options = {})
-
220
source = source.to_s
-
220
return "" unless source.present?
-
219
return source if source =~ URI_REGEXP
-
-
199
tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '')
-
-
199
if extname = compute_asset_extname(source, options)
-
49
source = "#{source}#{extname}"
-
end
-
-
199
if source[0] != ?/
-
173
source = compute_asset_path(source, options)
-
end
-
-
199
relative_url_root = (defined?(config.relative_url_root) && config.relative_url_root) ||
-
176
(respond_to?(:request) && request.try(:script_name))
-
199
if relative_url_root
-
23
source = "#{relative_url_root}#{source}" unless source.starts_with?("#{relative_url_root}/")
-
end
-
-
199
if host = compute_asset_host(source, options)
-
62
source = "#{host}#{source}"
-
end
-
-
199
"#{source}#{tail}"
-
end
-
1
alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with a asset_path named route
-
-
# Computes the full URL to a asset in the public directory. This
-
# will use +asset_path+ internally, so most of their behaviors
-
# will be the same.
-
1
def asset_url(source, options = {})
-
52
path_to_asset(source, options.merge(:protocol => :request))
-
end
-
1
alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route
-
-
1
ASSET_EXTENSIONS = {
-
javascript: '.js',
-
stylesheet: '.css'
-
}
-
-
# Compute extname to append to asset path. Returns nil if
-
# nothing should be added.
-
1
def compute_asset_extname(source, options = {})
-
199
return if options[:extname] == false
-
197
extname = options[:extname] || ASSET_EXTENSIONS[options[:type]]
-
197
extname if extname && File.extname(source) != extname
-
end
-
-
# Maps asset types to public directory.
-
1
ASSET_PUBLIC_DIRECTORIES = {
-
audio: '/audios',
-
font: '/fonts',
-
image: '/images',
-
javascript: '/javascripts',
-
stylesheet: '/stylesheets',
-
video: '/videos'
-
}
-
-
# Computes asset path to public directory. Plugins and
-
# extensions can override this method to point to custom assets
-
# or generate digested paths or query strings.
-
1
def compute_asset_path(source, options = {})
-
178
dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || ""
-
178
File.join(dir, source)
-
end
-
-
# Pick an asset host for this source. Returns +nil+ if no host is set,
-
# the host if no wildcard is set, the host interpolated with the
-
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
-
# or the value returned from invoking call on an object responding to call
-
# (proc or otherwise).
-
1
def compute_asset_host(source = "", options = {})
-
206
request = self.request if respond_to?(:request)
-
206
host = config.asset_host if defined? config.asset_host
-
206
host ||= request.base_url if request && options[:protocol] == :request
-
206
return unless host
-
-
68
if host.respond_to?(:call)
-
2
arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
-
2
args = [source]
-
2
args << request if request && (arity > 1 || arity < 0)
-
2
host = host.call(*args)
-
elsif host =~ /%d/
-
3
host = host % (Zlib.crc32(source) % 4)
-
end
-
-
68
if host =~ URI_REGEXP
-
49
host
-
else
-
19
protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative)
-
19
case protocol
-
when :relative
-
1
"//#{host}"
-
when :request
-
17
"#{request.protocol}#{host}"
-
else
-
1
"#{protocol}://#{host}"
-
end
-
end
-
end
-
-
# Computes the path to a javascript asset in the public javascripts directory.
-
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
-
# Full paths from the document root will be passed through.
-
# Used internally by javascript_include_tag to build the script path.
-
#
-
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
-
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
-
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
-
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
-
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
-
1
def javascript_path(source, options = {})
-
25
path_to_asset(source, {type: :javascript}.merge!(options))
-
end
-
1
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
-
-
# Computes the full URL to a javascript asset in the public javascripts directory.
-
# This will use +javascript_path+ internally, so most of their behaviors will be the same.
-
1
def javascript_url(source, options = {})
-
8
url_to_asset(source, {type: :javascript}.merge!(options))
-
end
-
1
alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
-
-
# Computes the path to a stylesheet asset in the public stylesheets directory.
-
# If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
-
# Full paths from the document root will be passed through.
-
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
-
#
-
# stylesheet_path "style" # => /stylesheets/style.css
-
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
-
# stylesheet_path "/dir/style.css" # => /dir/style.css
-
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
-
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
-
1
def stylesheet_path(source, options = {})
-
33
path_to_asset(source, {type: :stylesheet}.merge!(options))
-
end
-
1
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
-
-
# Computes the full URL to a stylesheet asset in the public stylesheets directory.
-
# This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
-
1
def stylesheet_url(source, options = {})
-
11
url_to_asset(source, {type: :stylesheet}.merge!(options))
-
end
-
1
alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
-
-
# Computes the path to an image asset.
-
# Full paths from the document root will be passed through.
-
# Used internally by +image_tag+ to build the image path:
-
#
-
# image_path("edit") # => "/assets/edit"
-
# image_path("edit.png") # => "/assets/edit.png"
-
# image_path("icons/edit.png") # => "/assets/icons/edit.png"
-
# image_path("/icons/edit.png") # => "/icons/edit.png"
-
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
-
#
-
# If you have images as application resources this method may conflict with their named routes.
-
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
-
# plugin authors are encouraged to do so.
-
1
def image_path(source, options = {})
-
45
path_to_asset(source, {type: :image}.merge!(options))
-
end
-
1
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
-
-
# Computes the full URL to an image asset.
-
# This will use +image_path+ internally, so most of their behaviors will be the same.
-
1
def image_url(source, options = {})
-
13
url_to_asset(source, {type: :image}.merge!(options))
-
end
-
1
alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
-
-
# Computes the path to a video asset in the public videos directory.
-
# Full paths from the document root will be passed through.
-
# Used internally by +video_tag+ to build the video path.
-
#
-
# video_path("hd") # => /videos/hd
-
# video_path("hd.avi") # => /videos/hd.avi
-
# video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
-
# video_path("/trailers/hd.avi") # => /trailers/hd.avi
-
# video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
-
1
def video_path(source, options = {})
-
26
path_to_asset(source, {type: :video}.merge!(options))
-
end
-
1
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
-
-
# Computes the full URL to a video asset in the public videos directory.
-
# This will use +video_path+ internally, so most of their behaviors will be the same.
-
1
def video_url(source, options = {})
-
8
url_to_asset(source, {type: :video}.merge!(options))
-
end
-
1
alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route
-
-
# Computes the path to an audio asset in the public audios directory.
-
# Full paths from the document root will be passed through.
-
# Used internally by +audio_tag+ to build the audio path.
-
#
-
# audio_path("horse") # => /audios/horse
-
# audio_path("horse.wav") # => /audios/horse.wav
-
# audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
-
# audio_path("/sounds/horse.wav") # => /sounds/horse.wav
-
# audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
-
1
def audio_path(source, options = {})
-
19
path_to_asset(source, {type: :audio}.merge!(options))
-
end
-
1
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
-
-
# Computes the full URL to an audio asset in the public audios directory.
-
# This will use +audio_path+ internally, so most of their behaviors will be the same.
-
1
def audio_url(source, options = {})
-
8
url_to_asset(source, {type: :audio}.merge!(options))
-
end
-
1
alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
-
-
# Computes the path to a font asset.
-
# Full paths from the document root will be passed through.
-
#
-
# font_path("font") # => /assets/font
-
# font_path("font.ttf") # => /assets/font.ttf
-
# font_path("dir/font.ttf") # => /assets/dir/font.ttf
-
# font_path("/dir/font.ttf") # => /dir/font.ttf
-
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
-
1
def font_path(source, options = {})
-
5
path_to_asset(source, {type: :font}.merge!(options))
-
end
-
1
alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
-
-
# Computes the full URL to a font asset.
-
# This will use +font_path+ internally, so most of their behaviors will be the same.
-
1
def font_url(source, options = {})
-
url_to_asset(source, {type: :font}.merge!(options))
-
end
-
1
alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module ActionView
-
# = Action View Atom Feed Helpers
-
1
module Helpers #:nodoc:
-
1
module AtomFeedHelper
-
# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
-
# template languages).
-
#
-
# Full usage example:
-
#
-
# config/routes.rb:
-
# Basecamp::Application.routes.draw do
-
# resources :posts
-
# root to: "posts#index"
-
# end
-
#
-
# app/controllers/posts_controller.rb:
-
# class PostsController < ApplicationController::Base
-
# # GET /posts.html
-
# # GET /posts.atom
-
# def index
-
# @posts = Post.all
-
#
-
# respond_to do |format|
-
# format.html
-
# format.atom
-
# end
-
# end
-
# end
-
#
-
# app/views/posts/index.atom.builder:
-
# atom_feed do |feed|
-
# feed.title("My great blog!")
-
# feed.updated(@posts[0].created_at) if @posts.length > 0
-
#
-
# @posts.each do |post|
-
# feed.entry(post) do |entry|
-
# entry.title(post.title)
-
# entry.content(post.body, type: 'html')
-
#
-
# entry.author do |author|
-
# author.name("DHH")
-
# end
-
# end
-
# end
-
# end
-
#
-
# The options for atom_feed are:
-
#
-
# * <tt>:language</tt>: Defaults to "en-US".
-
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
-
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
-
# * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
-
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
-
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
-
# 2005 is used (as an "I don't care" value).
-
# * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
-
#
-
# Other namespaces can be added to the root element:
-
#
-
# app/views/posts/index.atom.builder:
-
# atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
-
# 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
-
# feed.title("My great blog!")
-
# feed.updated((@posts.first.created_at))
-
# feed.tag!(openSearch:totalResults, 10)
-
#
-
# @posts.each do |post|
-
# feed.entry(post) do |entry|
-
# entry.title(post.title)
-
# entry.content(post.body, type: 'html')
-
# entry.tag!('app:edited', Time.now)
-
#
-
# entry.author do |author|
-
# author.name("DHH")
-
# end
-
# end
-
# end
-
# end
-
#
-
# The Atom spec defines five elements (content rights title subtitle
-
# summary) which may directly contain xhtml content if type: 'xhtml'
-
# is specified as an attribute. If so, this helper will take care of
-
# the enclosing div and xhtml namespace declaration. Example usage:
-
#
-
# entry.summary type: 'xhtml' do |xhtml|
-
# xhtml.p pluralize(order.line_items.count, "line item")
-
# xhtml.p "Shipped to #{order.address}"
-
# xhtml.p "Paid by #{order.pay_type}"
-
# end
-
#
-
#
-
# <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield
-
# an +AtomBuilder+ instance.
-
1
def atom_feed(options = {}, &block)
-
16
if options[:schema_date]
-
10
options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
-
else
-
6
options[:schema_date] = "2005" # The Atom spec copyright date
-
end
-
-
16
xml = options.delete(:xml) || eval("xml", block.binding)
-
16
xml.instruct!
-
16
if options[:instruct]
-
2
options[:instruct].each do |target,attrs|
-
2
if attrs.respond_to?(:keys)
-
1
xml.instruct!(target, attrs)
-
elsif attrs.respond_to?(:each)
-
3
attrs.each { |attr_group| xml.instruct!(target, attr_group) }
-
end
-
end
-
end
-
-
16
feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'}
-
69
feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}
-
-
16
xml.feed(feed_opts) do
-
16
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
-
16
xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
-
16
xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
-
-
16
yield AtomFeedBuilder.new(xml, self, options)
-
end
-
end
-
-
1
class AtomBuilder
-
1
XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
-
-
1
def initialize(xml)
-
32
@xml = xml
-
end
-
-
1
private
-
# Delegate to xml builder, first wrapping the element in a xhtml
-
# namespaced div element if the method and arguments indicate
-
# that an xhtml_block? is desired.
-
1
def method_missing(method, *arguments, &block)
-
117
if xhtml_block?(method, arguments)
-
2
@xml.__send__(method, *arguments) do
-
2
@xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml|
-
2
block.call(xhtml)
-
end
-
end
-
else
-
115
@xml.__send__(method, *arguments, &block)
-
end
-
end
-
-
# True if the method name matches one of the five elements defined
-
# in the Atom spec as potentially containing XHTML content and
-
# if type: 'xhtml' is, in fact, specified.
-
1
def xhtml_block?(method, arguments)
-
117
if XHTML_TAG_NAMES.include?(method.to_s)
-
80
last = arguments.last
-
80
last.is_a?(Hash) && last[:type].to_s == 'xhtml'
-
end
-
end
-
end
-
-
1
class AtomFeedBuilder < AtomBuilder
-
1
def initialize(xml, view, feed_options = {})
-
16
@xml, @view, @feed_options = xml, view, feed_options
-
end
-
-
# Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
-
1
def updated(date_or_time = nil)
-
16
@xml.updated((date_or_time || Time.now.utc).xmlschema)
-
end
-
-
# Creates an entry tag for a specific record and prefills the id using class and id.
-
#
-
# Options:
-
#
-
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
-
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
-
# * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
-
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
-
# * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
-
1
def entry(record, options = {})
-
32
@xml.entry do
-
32
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
-
-
32
if options[:published] || (record.respond_to?(:created_at) && record.created_at)
-
16
@xml.published((options[:published] || record.created_at).xmlschema)
-
end
-
-
32
if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
-
32
@xml.updated((options[:updated] || record.updated_at).xmlschema)
-
end
-
-
32
type = options.fetch(:type, 'text/html')
-
-
32
@xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
-
-
32
yield AtomBuilder.new(@xml)
-
end
-
end
-
end
-
-
end
-
end
-
end
-
1
require 'active_support/benchmarkable'
-
-
1
module ActionView
-
1
module Helpers
-
1
module BenchmarkHelper
-
1
include ActiveSupport::Benchmarkable
-
-
1
def benchmark(*)
-
6
capture { super }
-
end
-
end
-
end
-
end
-
1
module ActionView
-
# = Action View Cache Helper
-
1
module Helpers
-
1
module CacheHelper
-
# This helper exposes a method for caching fragments of a view
-
# rather than an entire action or page. This technique is useful
-
# caching pieces like menus, lists of newstopics, static HTML
-
# fragments, and so on. This method takes a block that contains
-
# the content you wish to cache.
-
#
-
# The best way to use this is by doing key-based cache expiration
-
# on top of a cache store like Memcached that'll automatically
-
# kick out old entries. For more on key-based expiration, see:
-
# http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
-
#
-
# When using this method, you list the cache dependency as the name of the cache, like so:
-
#
-
# <% cache project do %>
-
# <b>All the topics on this project</b>
-
# <%= render project.topics %>
-
# <% end %>
-
#
-
# This approach will assume that when a new topic is added, you'll touch
-
# the project. The cache key generated from this call will be something like:
-
#
-
# views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9
-
# ^class ^id ^updated_at ^template tree digest
-
#
-
# The cache is thus automatically bumped whenever the project updated_at is touched.
-
#
-
# If your template cache depends on multiple sources (try to avoid this to keep things simple),
-
# you can name all these dependencies as part of an array:
-
#
-
# <% cache [ project, current_user ] do %>
-
# <b>All the topics on this project</b>
-
# <%= render project.topics %>
-
# <% end %>
-
#
-
# This will include both records as part of the cache key and updating either of them will
-
# expire the cache.
-
#
-
# ==== Template digest
-
#
-
# The template digest that's added to the cache key is computed by taking an md5 of the
-
# contents of the entire template file. This ensures that your caches will automatically
-
# expire when you change the template file.
-
#
-
# Note that the md5 is taken of the entire template file, not just what's within the
-
# cache do/end call. So it's possible that changing something outside of that call will
-
# still expire the cache.
-
#
-
# Additionally, the digestor will automatically look through your template file for
-
# explicit and implicit dependencies, and include those as part of the digest.
-
#
-
# ==== Implicit dependencies
-
#
-
# Most template dependencies can be derived from calls to render in the template itself.
-
# Here are some examples of render calls that Cache Digests knows how to decode:
-
#
-
# render partial: "comments/comment", collection: commentable.comments
-
# render "comments/comments"
-
# render 'comments/comments'
-
# render('comments/comments')
-
#
-
# render "header" => render("comments/header")
-
#
-
# render(@topic) => render("topics/topic")
-
# render(topics) => render("topics/topic")
-
# render(message.topics) => render("topics/topic")
-
#
-
# It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived:
-
#
-
# render group_of_attachments
-
# render @project.documents.where(published: true).order('created_at')
-
#
-
# You will have to rewrite those to the explicit form:
-
#
-
# render partial: 'attachments/attachment', collection: group_of_attachments
-
# render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
-
#
-
# === Explicit dependencies
-
#
-
# Some times you'll have template dependencies that can't be derived at all. This is typically
-
# the case when you have template rendering that happens in helpers. Here's an example:
-
#
-
# <%= render_sortable_todolists @project.todolists %>
-
#
-
# You'll need to use a special comment format to call those out:
-
#
-
# <%# Template Dependency: todolists/todolist %>
-
# <%= render_sortable_todolists @project.todolists %>
-
#
-
# The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so.
-
# You can only declare one template dependency per line.
-
#
-
# === External dependencies
-
#
-
# If you use a helper method, for example, inside of a cached block and you then update that helper,
-
# you'll have to bump the cache as well. It doesn't really matter how you do it, but the md5 of the template file
-
# must change. One recommendation is to simply be explicit in a comment, like:
-
#
-
# <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
-
# <%= some_helper_method(person) %>
-
#
-
# Now all you'll have to do is change that timestamp when the helper method changes.
-
1
def cache(name = {}, options = nil, &block)
-
7
if controller.perform_caching
-
7
safe_concat(fragment_for(fragment_name_with_digest(name), options, &block))
-
else
-
yield
-
end
-
-
nil
-
end
-
-
1
def fragment_name_with_digest(name) #:nodoc:
-
7
if @virtual_path
-
[
-
*Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name),
-
5
Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context)
-
]
-
else
-
2
name
-
end
-
end
-
-
1
private
-
# TODO: Create an object that has caching read/write on it
-
1
def fragment_for(name = {}, options = nil, &block) #:nodoc:
-
10
if fragment = controller.read_fragment(name, options)
-
1
fragment
-
else
-
# VIEW TODO: Make #capture usable outside of ERB
-
# This dance is needed because Builder can't use capture
-
9
pos = output_buffer.length
-
9
yield
-
9
output_safe = output_buffer.html_safe?
-
9
fragment = output_buffer.slice!(pos..-1)
-
9
if output_safe
-
8
self.output_buffer = output_buffer.class.new(output_buffer)
-
end
-
9
controller.write_fragment(name, fragment, options)
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView
-
# = Action View Capture Helper
-
1
module Helpers
-
# CaptureHelper exposes methods to let you extract generated markup which
-
# can be used in other parts of a template or layout file.
-
#
-
# It provides a method to capture blocks into variables through capture and
-
# a way to capture a block of markup for use in a layout through content_for.
-
1
module CaptureHelper
-
# The capture method allows you to extract part of a template into a
-
# variable. You can then use this variable anywhere in your templates or layout.
-
#
-
# The capture method can be used in ERB templates...
-
#
-
# <% @greeting = capture do %>
-
# Welcome to my shiny new web page! The date and time is
-
# <%= Time.now %>
-
# <% end %>
-
#
-
# ...and Builder (RXML) templates.
-
#
-
# @timestamp = capture do
-
# "The current timestamp is #{Time.now}."
-
# end
-
#
-
# You can then use that variable anywhere else. For example:
-
#
-
# <html>
-
# <head><title><%= @greeting %></title></head>
-
# <body>
-
# <b><%= @greeting %></b>
-
# </body></html>
-
#
-
1
def capture(*args)
-
480
value = nil
-
960
buffer = with_output_buffer { value = yield(*args) }
-
480
if string = buffer.presence || value and string.is_a?(String)
-
370
ERB::Util.html_escape string
-
end
-
end
-
-
# Calling content_for stores a block of markup in an identifier for later use.
-
# You can make subsequent calls to the stored content in other templates, helper modules
-
# or the layout by passing the identifier as an argument to <tt>content_for</tt>.
-
#
-
# Note: <tt>yield</tt> can still be used to retrieve the stored content, but calling
-
# <tt>yield</tt> doesn't work in helper modules, while <tt>content_for</tt> does.
-
#
-
# ==== Examples
-
#
-
# <% content_for :not_authorized do %>
-
# alert('You are not authorized to do that!')
-
# <% end %>
-
#
-
# You can then use <tt>content_for :not_authorized</tt> anywhere in your templates.
-
#
-
# <%= content_for :not_authorized if current_user.nil? %>
-
#
-
# This is equivalent to:
-
#
-
# <%= yield :not_authorized if current_user.nil? %>
-
#
-
# <tt>content_for</tt>, however, can also be used in helper modules.
-
#
-
# module StorageHelper
-
# def stored_content
-
# content_for(:storage) || "Your storage is empty"
-
# end
-
# end
-
#
-
# This helper works just like normal helpers.
-
#
-
# <%= stored_content %>
-
#
-
# You can use the <tt>yield</tt> syntax alongside an existing call to <tt>yield</tt> in a layout. For example:
-
#
-
# <%# This is the layout %>
-
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-
# <head>
-
# <title>My Website</title>
-
# <%= yield :script %>
-
# </head>
-
# <body>
-
# <%= yield %>
-
# </body>
-
# </html>
-
#
-
# And now, we'll create a view that has a <tt>content_for</tt> call that
-
# creates the <tt>script</tt> identifier.
-
#
-
# <%# This is our view %>
-
# Please login!
-
#
-
# <% content_for :script do %>
-
# <script>alert('You are not authorized to view this page!')</script>
-
# <% end %>
-
#
-
# Then, in another view, you could to do something like this:
-
#
-
# <%= link_to 'Logout', action: 'logout', remote: true %>
-
#
-
# <% content_for :script do %>
-
# <%= javascript_include_tag :defaults %>
-
# <% end %>
-
#
-
# That will place +script+ tags for your default set of JavaScript files on the page;
-
# this technique is useful if you'll only be using these scripts in a few views.
-
#
-
# Note that content_for concatenates (default) the blocks it is given for a particular
-
# identifier in order. For example:
-
#
-
# <% content_for :navigation do %>
-
# <li><%= link_to 'Home', action: 'index' %></li>
-
# <% end %>
-
#
-
# <%# Add some other content, or use a different template: %>
-
#
-
# <% content_for :navigation do %>
-
# <li><%= link_to 'Login', action: 'login' %></li>
-
# <% end %>
-
#
-
# Then, in another template or layout, this code would render both links in order:
-
#
-
# <ul><%= content_for :navigation %></ul>
-
#
-
# If the flush parameter is true content_for replaces the blocks it is given. For example:
-
#
-
# <% content_for :navigation do %>
-
# <li><%= link_to 'Home', action: 'index' %></li>
-
# <% end %>
-
#
-
# <%# Add some other content, or use a different template: %>
-
#
-
# <% content_for :navigation, flush: true do %>
-
# <li><%= link_to 'Login', action: 'login' %></li>
-
# <% end %>
-
#
-
# Then, in another template or layout, this code would render only the last link:
-
#
-
# <ul><%= content_for :navigation %></ul>
-
#
-
# Lastly, simple content can be passed as a parameter:
-
#
-
# <% content_for :script, javascript_include_tag(:defaults) %>
-
#
-
# WARNING: content_for is ignored in caches. So you shouldn't use it
-
# for elements that will be fragment cached.
-
1
def content_for(name, content = nil, options = {}, &block)
-
59
if content || block_given?
-
44
if block_given?
-
24
options = content if content
-
24
content = capture(&block)
-
end
-
44
if content
-
40
options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content)
-
end
-
nil
-
else
-
15
@view_flow.get(name)
-
end
-
end
-
-
# The same as +content_for+ but when used with streaming flushes
-
# straight back to the layout. In other words, if you want to
-
# concatenate several times to the same buffer when rendering a given
-
# template, you should use +content_for+, if not, use +provide+ to tell
-
# the layout to stop looking for more contents.
-
1
def provide(name, content = nil, &block)
-
7
content = capture(&block) if block_given?
-
7
result = @view_flow.append!(name, content) if content
-
7
result unless content
-
end
-
-
# content_for? simply checks whether any content has been captured yet using content_for
-
# Useful to render parts of your layout differently based on what is in your views.
-
#
-
# ==== Examples
-
#
-
# Perhaps you will use different css in you layout if no content_for :right_column
-
#
-
# <%# This is the layout %>
-
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-
# <head>
-
# <title>My Website</title>
-
# <%= yield :script %>
-
# </head>
-
# <body class="<%= content_for?(:right_col) ? 'one-column' : 'two-column' %>">
-
# <%= yield %>
-
# <%= yield :right_col %>
-
# </body>
-
# </html>
-
1
def content_for?(name)
-
14
@view_flow.get(name).present?
-
end
-
-
# Use an alternate output buffer for the duration of the block.
-
# Defaults to a new empty string.
-
1
def with_output_buffer(buf = nil) #:nodoc:
-
485
unless buf
-
484
buf = ActionView::OutputBuffer.new
-
484
buf.force_encoding(output_buffer.encoding) if output_buffer
-
end
-
485
self.output_buffer, old_buffer = buf, output_buffer
-
485
yield
-
485
output_buffer
-
ensure
-
485
self.output_buffer = old_buffer
-
end
-
-
# Add the output buffer to the response body and start a new one.
-
1
def flush_output_buffer #:nodoc:
-
7
if output_buffer && !output_buffer.empty?
-
4
response.stream.write output_buffer
-
4
self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0]
-
nil
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/attr_internal'
-
-
1
module ActionView
-
1
module Helpers
-
# This module keeps all methods and behavior in ActionView
-
# that simply delegates to the controller.
-
1
module ControllerHelper #:nodoc:
-
1
attr_internal :controller, :request
-
-
1
delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
-
:flash, :action_name, :controller_name, :controller_path, :to => :controller
-
-
1
def assign_controller(controller)
-
1577
if @_controller = controller
-
1302
@_request = controller.request if controller.respond_to?(:request)
-
1302
@_config = controller.config.inheritable_copy if controller.respond_to?(:config)
-
end
-
end
-
-
1
def logger
-
4
controller.logger if controller.respond_to?(:logger)
-
end
-
end
-
end
-
end
-
1
module ActionView
-
# = Action View CSRF Helper
-
1
module Helpers
-
1
module CsrfHelper
-
# Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site
-
# request forgery protection parameter and token, respectively.
-
#
-
# <head>
-
# <%= csrf_meta_tags %>
-
# </head>
-
#
-
# These are used to generate the dynamic forms that implement non-remote links with
-
# <tt>:method</tt>.
-
#
-
# Note that regular forms generate hidden fields, and that Ajax calls are whitelisted,
-
# so they do not use these tags.
-
1
def csrf_meta_tags
-
2
if protect_against_forgery?
-
[
-
1
tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token),
-
tag('meta', :name => 'csrf-token', :content => form_authenticity_token)
-
].join("\n").html_safe
-
end
-
end
-
-
# For backwards compatibility.
-
1
alias csrf_meta_tag csrf_meta_tags
-
end
-
end
-
end
-
1
require 'date'
-
1
require 'action_view/helpers/tag_helper'
-
1
require 'active_support/core_ext/date/conversions'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/object/with_options'
-
-
1
module ActionView
-
1
module Helpers
-
# = Action View Date Helpers
-
#
-
# The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time
-
# elements. All of the select-type methods share a number of common options that are as follows:
-
#
-
# * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
-
# would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> method.
-
# * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
-
# * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true,
-
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead
-
# of \date[month].
-
1
module DateHelper
-
# Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
-
# Pass <tt>include_seconds: true</tt> if you want more detailed approximations when distance < 1 min, 29 secs.
-
# Distances are reported based on the following table:
-
#
-
# 0 <-> 29 secs # => less than a minute
-
# 30 secs <-> 1 min, 29 secs # => 1 minute
-
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
-
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
-
# 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
-
# 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day
-
# 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
-
# 29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs # => about 1 month
-
# 44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 2 months
-
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
-
# 1 yr <-> 1 yr, 3 months # => about 1 year
-
# 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year
-
# 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years
-
# 2 yrs <-> max time or date # => (same rules as 1 yr)
-
#
-
# With <tt>include_seconds: true</tt> and the difference < 1 minute 29 seconds:
-
# 0-4 secs # => less than 5 seconds
-
# 5-9 secs # => less than 10 seconds
-
# 10-19 secs # => less than 20 seconds
-
# 20-39 secs # => half a minute
-
# 40-59 secs # => less than a minute
-
# 60-89 secs # => 1 minute
-
#
-
# ==== Examples
-
# from_time = Time.now
-
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
-
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
-
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
-
# distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds
-
# distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
-
# distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
-
# distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute
-
# distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute
-
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
-
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
-
# distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
-
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years
-
#
-
# to_time = Time.now + 6.years + 19.days
-
# distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years
-
# distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years
-
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
-
1
def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
-
307
if include_seconds_or_options.is_a?(Hash)
-
307
options = include_seconds_or_options
-
else
-
ActiveSupport::Deprecation.warn "distance_of_time_in_words and time_ago_in_words now accept :include_seconds " +
-
"as a part of options hash, not a boolean argument"
-
options[:include_seconds] ||= !!include_seconds_or_options
-
end
-
-
307
options = {
-
scope: :'datetime.distance_in_words'
-
}.merge!(options)
-
-
307
from_time = from_time.to_time if from_time.respond_to?(:to_time)
-
307
to_time = to_time.to_time if to_time.respond_to?(:to_time)
-
307
from_time, to_time = to_time, from_time if from_time > to_time
-
307
distance_in_minutes = ((to_time - from_time)/60.0).round
-
307
distance_in_seconds = (to_time - from_time).round
-
-
307
I18n.with_options :locale => options[:locale], :scope => options[:scope] do |locale|
-
307
case distance_in_minutes
-
when 0..1
-
return distance_in_minutes == 0 ?
-
locale.t(:less_than_x_minutes, :count => 1) :
-
139
locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds]
-
-
63
case distance_in_seconds
-
9
when 0..4 then locale.t :less_than_x_seconds, :count => 5
-
9
when 5..9 then locale.t :less_than_x_seconds, :count => 10
-
14
when 10..19 then locale.t :less_than_x_seconds, :count => 20
-
10
when 20..39 then locale.t :half_a_minute
-
11
when 40..59 then locale.t :less_than_x_minutes, :count => 1
-
10
else locale.t :x_minutes, :count => 1
-
end
-
-
13
when 2...45 then locale.t :x_minutes, :count => distance_in_minutes
-
13
when 45...90 then locale.t :about_x_hours, :count => 1
-
# 90 mins up to 24 hours
-
12
when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
-
# 24 hours up to 42 hours
-
9
when 1440...2520 then locale.t :x_days, :count => 1
-
# 42 hours up to 30 days
-
12
when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
-
# 30 days up to 60 days
-
18
when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round
-
# 60 days up to 365 days
-
10
when 86400...525600 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
-
else
-
81
if from_time.acts_like?(:time) && to_time.acts_like?(:time)
-
79
fyear = from_time.year
-
79
fyear += 1 if from_time.month >= 3
-
79
tyear = to_time.year
-
79
tyear -= 1 if to_time.month < 3
-
522
leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
-
79
minute_offset_for_leap_year = leap_years * 1440
-
# Discount the leap year days when calculating year distance.
-
# e.g. if there are 20 leap year days between 2 dates having the same day
-
# and month then the based on 365 days calculation
-
# the distance in years will come out to over 80 years when in written
-
# english it would read better as about 80 years.
-
79
minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
-
else
-
2
minutes_with_offset = distance_in_minutes
-
end
-
81
remainder = (minutes_with_offset % 525600)
-
81
distance_in_years = (minutes_with_offset / 525600)
-
81
if remainder < 131400
-
25
locale.t(:about_x_years, :count => distance_in_years)
-
elsif remainder < 394200
-
29
locale.t(:over_x_years, :count => distance_in_years)
-
else
-
27
locale.t(:almost_x_years, :count => distance_in_years + 1)
-
end
-
end
-
end
-
end
-
-
# Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
-
#
-
# time_ago_in_words(3.minutes.from_now) # => 3 minutes
-
# time_ago_in_words(3.minutes.ago) # => 3 minutes
-
# time_ago_in_words(Time.now - 15.hours) # => about 15 hours
-
# time_ago_in_words(Time.now) # => less than a minute
-
# time_ago_in_words(Time.now, include_seconds: true) # => less than 5 seconds
-
#
-
# from_time = Time.now - 3.days - 14.minutes - 25.seconds
-
# time_ago_in_words(from_time) # => 3 days
-
#
-
# from_time = (3.days + 14.minutes + 25.seconds).ago
-
# time_ago_in_words(from_time) # => 3 days
-
#
-
# Note that you cannot pass a <tt>Numeric</tt> value to <tt>time_ago_in_words</tt>.
-
#
-
1
def time_ago_in_words(from_time, include_seconds_or_options = {})
-
4
distance_of_time_in_words(from_time, Time.now, include_seconds_or_options)
-
end
-
-
1
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
-
-
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
-
# attribute (identified by +method+) on an object assigned to the template (identified by +object+).
-
#
-
#
-
# ==== Options
-
# * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
-
# "2" instead of "February").
-
# * <tt>:use_two_digit_numbers</tt> - Set to true if you want to display two digit month and day numbers (e.g.
-
# "02" instead of "February" and "08" instead of "8").
-
# * <tt>:use_short_month</tt> - Set to true if you want to use abbreviated month names instead of full
-
# month names (e.g. "Feb" instead of "February").
-
# * <tt>:add_month_numbers</tt> - Set to true if you want to use both month numbers and month names (e.g.
-
# "2 - February" instead of "February").
-
# * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names.
-
# Note: You can also use Rails' i18n functionality for this.
-
# * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
-
# * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Time.now.year - 5</tt>.
-
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>.
-
# * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
-
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
-
# first of the given month in order to not create invalid dates like 31 February.
-
# * <tt>:discard_month</tt> - Set to true if you don't want to show a month select. This includes the month
-
# as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true.
-
# * <tt>:discard_year</tt> - Set to true if you don't want to show a year select. This includes the year
-
# as a hidden field instead of showing a select field.
-
# * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> to
-
# customize the order in which the select fields are shown. If you leave out any of the symbols, the respective
-
# select will not be shown (like when you set <tt>discard_xxx: true</tt>. Defaults to the order defined in
-
# the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).
-
# * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
-
# dates.
-
# * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
-
# * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
-
# * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings
-
# for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
-
# Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
-
# or the given prompt string.
-
# * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option
-
# automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags.
-
#
-
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
-
#
-
# NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute.
-
# date_select("article", "written_on")
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
-
# # with the year in the year drop down box starting at 1995.
-
# date_select("article", "written_on", start_year: 1995)
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
-
# # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
-
# # and without a day select box.
-
# date_select("article", "written_on", start_year: 1995, use_month_numbers: true,
-
# discard_day: true, include_blank: true)
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
-
# # with two digit numbers used for months and days.
-
# date_select("article", "written_on", use_two_digit_numbers: true)
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
-
# # with the fields ordered as day, month, year rather than month, day, year.
-
# date_select("article", "written_on", order: [:day, :month, :year])
-
#
-
# # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
-
# # lacking a year field.
-
# date_select("user", "birthday", order: [:month, :day])
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
-
# # which is initially set to the date 3 days from the current date
-
# date_select("article", "written_on", default: 3.days.from_now)
-
#
-
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
-
# # that will have a default day of 20.
-
# date_select("credit_card", "bill_due", default: { day: 20 })
-
#
-
# # Generates a date select with custom prompts.
-
# date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' })
-
#
-
# The selects are prepared for multi-parameter assignment to an Active Record object.
-
#
-
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
-
# all month choices are valid.
-
1
def date_select(object_name, method, options = {}, html_options = {})
-
29
Tags::DateSelect.new(object_name, method, self, options, html_options).render
-
end
-
-
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
-
# specified time-based attribute (identified by +method+) on an object assigned to the template (identified by
-
# +object+). You can include the seconds with <tt>:include_seconds</tt>. You can get hours in the AM/PM format
-
# with <tt>:ampm</tt> option.
-
#
-
# This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
-
# <tt>:ignore_date</tt> is set to +true+. If you set the <tt>:ignore_date</tt> to +true+, you must have a
-
# +date_select+ on the same method within the form otherwise an exception will be raised.
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute.
-
# time_select("article", "sunrise")
-
#
-
# # Creates a time select tag with a seconds field that, when POSTed, will be stored in the article variables in
-
# # the sunrise attribute.
-
# time_select("article", "start_time", include_seconds: true)
-
#
-
# # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45.
-
# time_select 'game', 'game_time', {minute_step: 15}
-
#
-
# # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
-
# time_select("article", "written_on", prompt: {hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds'})
-
# time_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
-
# time_select("article", "written_on", prompt: true) # generic prompts for all
-
#
-
# # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
-
# time_select 'game', 'game_time', {ampm: true}
-
#
-
# The selects are prepared for multi-parameter assignment to an Active Record object.
-
#
-
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
-
# all month choices are valid.
-
1
def time_select(object_name, method, options = {}, html_options = {})
-
12
Tags::TimeSelect.new(object_name, method, self, options, html_options).render
-
end
-
-
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
-
# specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified
-
# by +object+).
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on
-
# # attribute.
-
# datetime_select("article", "written_on")
-
#
-
# # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
-
# # article variable in the written_on attribute.
-
# datetime_select("article", "written_on", start_year: 1995)
-
#
-
# # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will
-
# # be stored in the trip variable in the departing attribute.
-
# datetime_select("trip", "departing", default: 3.days.from_now)
-
#
-
# # Generate a datetime select with hours in the AM/PM format
-
# datetime_select("article", "written_on", ampm: true)
-
#
-
# # Generates a datetime select that discards the type that, when POSTed, will be stored in the article variable
-
# # as the written_on attribute.
-
# datetime_select("article", "written_on", discard_type: true)
-
#
-
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
-
# datetime_select("article", "written_on", prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
-
# datetime_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
-
# datetime_select("article", "written_on", prompt: true) # generic prompts for all
-
#
-
# The selects are prepared for multi-parameter assignment to an Active Record object.
-
1
def datetime_select(object_name, method, options = {}, html_options = {})
-
30
Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render
-
end
-
-
# Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
-
# +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with
-
# an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not
-
# supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add
-
# <tt>:date_separator</tt>, <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to
-
# control visual display of the elements.
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# my_date_time = Time.now + 4.days
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today).
-
# select_datetime(my_date_time)
-
#
-
# # Generates a datetime select that defaults to today (no specified datetime)
-
# select_datetime()
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
-
# # with the fields ordered year, month, day rather than month, day, year.
-
# select_datetime(my_date_time, order: [:year, :month, :day])
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
-
# # with a '/' between each date field.
-
# select_datetime(my_date_time, date_separator: '/')
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
-
# # with a date fields separated by '/', time fields separated by '' and the date and time fields
-
# # separated by a comma (',').
-
# select_datetime(my_date_time, date_separator: '/', time_separator: '', datetime_separator: ',')
-
#
-
# # Generates a datetime select that discards the type of the field and defaults to the datetime in
-
# # my_date_time (four days after today)
-
# select_datetime(my_date_time, discard_type: true)
-
#
-
# # Generate a datetime field with hours in the AM/PM format
-
# select_datetime(my_date_time, ampm: true)
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
-
# # prefixed with 'payday' rather than 'date'
-
# select_datetime(my_date_time, prefix: 'payday')
-
#
-
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
-
# select_datetime(my_date_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
-
# select_datetime(my_date_time, prompt: {hour: true}) # generic prompt for hours
-
# select_datetime(my_date_time, prompt: true) # generic prompts for all
-
1
def select_datetime(datetime = Time.current, options = {}, html_options = {})
-
14
DateTimeSelector.new(datetime, options, html_options).select_datetime
-
end
-
-
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
-
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
-
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order.
-
# If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden.
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# my_date = Time.now + 6.days
-
#
-
# # Generates a date select that defaults to the date in my_date (six days after today).
-
# select_date(my_date)
-
#
-
# # Generates a date select that defaults to today (no specified date).
-
# select_date()
-
#
-
# # Generates a date select that defaults to the date in my_date (six days after today)
-
# # with the fields ordered year, month, day rather than month, day, year.
-
# select_date(my_date, order: [:year, :month, :day])
-
#
-
# # Generates a date select that discards the type of the field and defaults to the date in
-
# # my_date (six days after today).
-
# select_date(my_date, discard_type: true)
-
#
-
# # Generates a date select that defaults to the date in my_date,
-
# # which has fields separated by '/'.
-
# select_date(my_date, date_separator: '/')
-
#
-
# # Generates a date select that defaults to the datetime in my_date (six days after today)
-
# # prefixed with 'payday' rather than 'date'.
-
# select_date(my_date, prefix: 'payday')
-
#
-
# # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
-
# select_date(my_date, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
-
# select_date(my_date, prompt: {hour: true}) # generic prompt for hours
-
# select_date(my_date, prompt: true) # generic prompts for all
-
1
def select_date(date = Date.current, options = {}, html_options = {})
-
28
DateTimeSelector.new(date, options, html_options).select_date
-
end
-
-
# Returns a set of html select-tags (one for hour and minute).
-
# You can set <tt>:time_separator</tt> key to format the output, and
-
# the <tt>:include_seconds</tt> option to include an input for seconds.
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds
-
#
-
# # Generates a time select that defaults to the time in my_time.
-
# select_time(my_time)
-
#
-
# # Generates a time select that defaults to the current time (no specified time).
-
# select_time()
-
#
-
# # Generates a time select that defaults to the time in my_time,
-
# # which has fields separated by ':'.
-
# select_time(my_time, time_separator: ':')
-
#
-
# # Generates a time select that defaults to the time in my_time,
-
# # that also includes an input for seconds.
-
# select_time(my_time, include_seconds: true)
-
#
-
# # Generates a time select that defaults to the time in my_time, that has fields
-
# # separated by ':' and includes an input for seconds.
-
# select_time(my_time, time_separator: ':', include_seconds: true)
-
#
-
# # Generate a time select field with hours in the AM/PM format
-
# select_time(my_time, ampm: true)
-
#
-
# # Generates a time select field with hours that range from 2 to 14
-
# select_time(my_time, start_hour: 2, end_hour: 14)
-
#
-
# # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
-
# select_time(my_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
-
# select_time(my_time, prompt: {hour: true}) # generic prompt for hours
-
# select_time(my_time, prompt: true) # generic prompts for all
-
1
def select_time(datetime = Time.current, options = {}, html_options = {})
-
16
DateTimeSelector.new(datetime, options, html_options).select_time
-
end
-
-
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
-
# The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
-
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
-
#
-
# my_time = Time.now + 16.minutes
-
#
-
# # Generates a select field for seconds that defaults to the seconds for the time in my_time.
-
# select_second(my_time)
-
#
-
# # Generates a select field for seconds that defaults to the number given.
-
# select_second(33)
-
#
-
# # Generates a select field for seconds that defaults to the seconds for the time in my_time
-
# # that is named 'interval' rather than 'second'.
-
# select_second(my_time, field_name: 'interval')
-
#
-
# # Generates a select field for seconds with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_second(14, prompt: 'Choose seconds')
-
1
def select_second(datetime, options = {}, html_options = {})
-
9
DateTimeSelector.new(datetime, options, html_options).select_second
-
end
-
-
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
-
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
-
# selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
-
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
-
#
-
# my_time = Time.now + 6.hours
-
#
-
# # Generates a select field for minutes that defaults to the minutes for the time in my_time.
-
# select_minute(my_time)
-
#
-
# # Generates a select field for minutes that defaults to the number given.
-
# select_minute(14)
-
#
-
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
-
# # that is named 'moment' rather than 'minute'.
-
# select_minute(my_time, field_name: 'moment')
-
#
-
# # Generates a select field for minutes with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_minute(14, prompt: 'Choose minutes')
-
1
def select_minute(datetime, options = {}, html_options = {})
-
14
DateTimeSelector.new(datetime, options, html_options).select_minute
-
end
-
-
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
-
# The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
-
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
-
#
-
# my_time = Time.now + 6.hours
-
#
-
# # Generates a select field for hours that defaults to the hour for the time in my_time.
-
# select_hour(my_time)
-
#
-
# # Generates a select field for hours that defaults to the number given.
-
# select_hour(13)
-
#
-
# # Generates a select field for hours that defaults to the hour for the time in my_time
-
# # that is named 'stride' rather than 'hour'.
-
# select_hour(my_time, field_name: 'stride')
-
#
-
# # Generates a select field for hours with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_hour(13, prompt: 'Choose hour')
-
#
-
# # Generate a select field for hours in the AM/PM format
-
# select_hour(my_time, ampm: true)
-
#
-
# # Generates a select field that includes options for hours from 2 to 14.
-
# select_hour(my_time, start_hour: 2, end_hour: 14)
-
1
def select_hour(datetime, options = {}, html_options = {})
-
9
DateTimeSelector.new(datetime, options, html_options).select_hour
-
end
-
-
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
-
# The <tt>date</tt> can also be substituted for a day number.
-
# If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
-
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
-
#
-
# my_date = Time.now + 2.days
-
#
-
# # Generates a select field for days that defaults to the day for the date in my_date.
-
# select_day(my_time)
-
#
-
# # Generates a select field for days that defaults to the number given.
-
# select_day(5)
-
#
-
# # Generates a select field for days that defaults to the number given, but displays it with two digits.
-
# select_day(5, use_two_digit_numbers: true)
-
#
-
# # Generates a select field for days that defaults to the day for the date in my_date
-
# # that is named 'due' rather than 'day'.
-
# select_day(my_time, field_name: 'due')
-
#
-
# # Generates a select field for days with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_day(5, prompt: 'Choose day')
-
1
def select_day(date, options = {}, html_options = {})
-
12
DateTimeSelector.new(date, options, html_options).select_day
-
end
-
-
# Returns a select tag with options for each of the months January through December with the current month
-
# selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are
-
# used as values (what's submitted to the server). It's also possible to use month numbers for the presentation
-
# instead of names -- set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you
-
# want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer
-
# to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want
-
# to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
-
# If you want to display months with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
-
# Override the field name using the <tt>:field_name</tt> option, 'month' by default.
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "January", "March".
-
# select_month(Date.today)
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # is named "start" rather than "month".
-
# select_month(Date.today, field_name: 'start')
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "1", "3".
-
# select_month(Date.today, use_month_numbers: true)
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "1 - January", "3 - March".
-
# select_month(Date.today, add_month_numbers: true)
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "Jan", "Mar".
-
# select_month(Date.today, use_short_month: true)
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "Januar", "Marts."
-
# select_month(Date.today, use_month_names: %w(Januar Februar Marts ...))
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys with two digit numbers like "01", "03".
-
# select_month(Date.today, use_two_digit_numbers: true)
-
#
-
# # Generates a select field for months with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_month(14, prompt: 'Choose month')
-
1
def select_month(date, options = {}, html_options = {})
-
33
DateTimeSelector.new(date, options, html_options).select_month
-
end
-
-
# Returns a select tag with options for each of the five years on each side of the current, which is selected.
-
# The five year radius can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the
-
# +options+. Both ascending and descending year lists are supported by making <tt>:start_year</tt> less than or
-
# greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number.
-
# Override the field name using the <tt>:field_name</tt> option, 'year' by default.
-
#
-
# # Generates a select field for years that defaults to the current year that
-
# # has ascending year values.
-
# select_year(Date.today, start_year: 1992, end_year: 2007)
-
#
-
# # Generates a select field for years that defaults to the current year that
-
# # is named 'birth' rather than 'year'.
-
# select_year(Date.today, field_name: 'birth')
-
#
-
# # Generates a select field for years that defaults to the current year that
-
# # has descending year values.
-
# select_year(Date.today, start_year: 2005, end_year: 1900)
-
#
-
# # Generates a select field for years that defaults to the year 2006 that
-
# # has ascending year values.
-
# select_year(2006, start_year: 2000, end_year: 2010)
-
#
-
# # Generates a select field for years with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_year(14, prompt: 'Choose year')
-
1
def select_year(date, options = {}, html_options = {})
-
16
DateTimeSelector.new(date, options, html_options).select_year
-
end
-
-
# Returns an html time tag for the given date or time.
-
#
-
# time_tag Date.today # =>
-
# <time datetime="2010-11-04">November 04, 2010</time>
-
# time_tag Time.now # =>
-
# <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
-
# time_tag Date.yesterday, 'Yesterday' # =>
-
# <time datetime="2010-11-03">Yesterday</time>
-
# time_tag Date.today, pubdate: true # =>
-
# <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
-
#
-
# <%= time_tag Time.now do %>
-
# <span>Right now</span>
-
# <% end %>
-
# # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time>
-
1
def time_tag(date_or_time, *args, &block)
-
6
options = args.extract_options!
-
6
format = options.delete(:format) || :long
-
6
content = args.first || I18n.l(date_or_time, :format => format)
-
6
datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339
-
-
6
content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
-
end
-
end
-
-
1
class DateTimeSelector #:nodoc:
-
1
include ActionView::Helpers::TagHelper
-
-
1
DEFAULT_PREFIX = 'date'.freeze
-
1
POSITION = {
-
:year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
-
}.freeze
-
-
1
AMPM_TRANSLATION = Hash[
-
[[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"],
-
[4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"],
-
[8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"],
-
[12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"],
-
[16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"],
-
[20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]]
-
].freeze
-
-
1
def initialize(datetime, options = {}, html_options = {})
-
222
@options = options.dup
-
222
@html_options = html_options.dup
-
222
@datetime = datetime
-
222
@options[:datetime_separator] ||= ' — '
-
222
@options[:time_separator] ||= ' : '
-
end
-
-
1
def select_datetime
-
44
order = date_order.dup
-
43
order -= [:hour, :minute, :second]
-
43
@options[:discard_year] ||= true unless order.include?(:year)
-
43
@options[:discard_month] ||= true unless order.include?(:month)
-
43
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
-
43
@options[:discard_minute] ||= true if @options[:discard_hour]
-
43
@options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
-
-
43
set_day_if_discarded
-
-
43
if @options[:tag] && @options[:ignore_date]
-
select_time
-
else
-
172
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
-
43
order += [:hour, :minute, :second] unless @options[:discard_hour]
-
-
43
build_selects_from_types(order)
-
end
-
end
-
-
1
def select_date
-
57
order = date_order.dup
-
-
57
@options[:discard_hour] = true
-
57
@options[:discard_minute] = true
-
57
@options[:discard_second] = true
-
-
57
@options[:discard_year] ||= true unless order.include?(:year)
-
57
@options[:discard_month] ||= true unless order.include?(:month)
-
57
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
-
-
57
set_day_if_discarded
-
-
228
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
-
-
57
build_selects_from_types(order)
-
end
-
-
1
def select_time
-
28
order = []
-
-
28
@options[:discard_month] = true
-
28
@options[:discard_year] = true
-
28
@options[:discard_day] = true
-
28
@options[:discard_second] ||= true unless @options[:include_seconds]
-
-
28
order += [:year, :month, :day] unless @options[:ignore_date]
-
-
28
order += [:hour, :minute]
-
28
order << :second if @options[:include_seconds]
-
-
28
build_selects_from_types(order)
-
end
-
-
1
def select_second
-
59
if @options[:use_hidden] || @options[:discard_second]
-
37
build_hidden(:second, sec) if @options[:include_seconds]
-
else
-
22
build_options_and_select(:second, sec)
-
end
-
end
-
-
1
def select_minute
-
84
if @options[:use_hidden] || @options[:discard_minute]
-
9
build_hidden(:minute, min)
-
else
-
75
build_options_and_select(:minute, min, :step => @options[:minute_step])
-
end
-
end
-
-
1
def select_hour
-
79
if @options[:use_hidden] || @options[:discard_hour]
-
4
build_hidden(:hour, hour)
-
else
-
75
options = {}
-
75
options[:ampm] = @options[:ampm] || false
-
75
options[:start] = @options[:start_hour] || 0
-
75
options[:end] = @options[:end_hour] || 23
-
75
build_options_and_select(:hour, hour, options)
-
end
-
end
-
-
1
def select_day
-
136
if @options[:use_hidden] || @options[:discard_day]
-
44
build_hidden(:day, day || 1)
-
else
-
92
build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers])
-
end
-
end
-
-
1
def select_month
-
157
if @options[:use_hidden] || @options[:discard_month]
-
41
build_hidden(:month, month || 1)
-
else
-
116
month_options = []
-
116
1.upto(12) do |month_number|
-
1392
options = { :value => month_number }
-
1392
options[:selected] = "selected" if month == month_number
-
1392
month_options << content_tag(:option, month_name(month_number), options) + "\n"
-
end
-
116
build_select(:month, month_options.join)
-
end
-
end
-
-
1
def select_year
-
142
if !@datetime || @datetime == 0
-
21
val = '1'
-
21
middle_year = Date.today.year
-
else
-
121
val = middle_year = year
-
end
-
-
142
if @options[:use_hidden] || @options[:discard_year]
-
40
build_hidden(:year, val)
-
else
-
102
options = {}
-
102
options[:start] = @options[:start_year] || middle_year - 5
-
102
options[:end] = @options[:end_year] || middle_year + 5
-
102
options[:step] = options[:start] < options[:end] ? 1 : -1
-
102
options[:leading_zeros] = false
-
102
options[:max_years_allowed] = @options[:max_years_allowed] || 1000
-
-
102
if (options[:end] - options[:start]).abs > options[:max_years_allowed]
-
2
raise ArgumentError, "There're too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter"
-
end
-
-
100
build_options_and_select(:year, val, options)
-
end
-
end
-
-
1
private
-
1
%w( sec min hour day month year ).each do |method|
-
6
define_method(method) do
-
1875
@datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime
-
end
-
end
-
-
# If the day is hidden, the day should be set to the 1st so all month and year choices are
-
# valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid.
-
1
def set_day_if_discarded
-
100
if @datetime && @options[:discard_day]
-
12
@datetime = @datetime.change(:day => 1)
-
end
-
end
-
-
# Returns translated month names, but also ensures that a custom month
-
# name array has a leading nil element.
-
1
def month_names
-
@month_names ||= begin
-
112
month_names = @options[:use_month_names] || translated_month_names
-
112
month_names.unshift(nil) if month_names.size < 13
-
112
month_names
-
1344
end
-
end
-
-
# Returns translated month names.
-
# => [nil, "January", "February", "March",
-
# "April", "May", "June", "July",
-
# "August", "September", "October",
-
# "November", "December"]
-
#
-
# If <tt>:use_short_month</tt> option is set
-
# => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-
# "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
-
1
def translated_month_names
-
107
key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
-
107
I18n.translate(key, :locale => @options[:locale])
-
end
-
-
# Lookup month name for number.
-
# month_name(1) => "January"
-
#
-
# If <tt>:use_month_numbers</tt> option is passed
-
# month_name(1) => 1
-
#
-
# If <tt>:use_two_month_numbers</tt> option is passed
-
# month_name(1) => '01'
-
#
-
# If <tt>:add_month_numbers</tt> option is passed
-
# month_name(1) => "1 - January"
-
1
def month_name(number)
-
1392
if @options[:use_month_numbers]
-
24
number
-
1368
elsif @options[:use_two_digit_numbers]
-
24
sprintf "%02d", number
-
1344
elsif @options[:add_month_numbers]
-
48
"#{number} - #{month_names[number]}"
-
else
-
1296
month_names[number]
-
end
-
end
-
-
1
def date_order
-
101
@date_order ||= @options[:order] || translated_date_order
-
end
-
-
1
def translated_date_order
-
82
date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
-
-
82
forbidden_elements = date_order - [:year, :month, :day]
-
82
if forbidden_elements.any?
-
1
raise StandardError,
-
"#{@options[:locale]}.date.order only accepts :year, :month and :day"
-
end
-
-
81
date_order
-
end
-
-
# Build full select tag from date type and options.
-
1
def build_options_and_select(type, selected, options = {})
-
364
build_select(type, build_options(selected, options))
-
end
-
-
# Build select option html from date value and options.
-
# build_options(15, start: 1, end: 31)
-
# => "<option value="1">1</option>
-
# <option value="2">2</option>
-
# <option value="3">3</option>..."
-
#
-
# If <tt>use_two_digit_numbers: true</tt> option is passed
-
# build_options(15, start: 1, end: 31, use_two_digit_numbers: true)
-
# => "<option value="1">01</option>
-
# <option value="2">02</option>
-
# <option value="3">03</option>..."
-
#
-
# If <tt>:step</tt> options is passed
-
# build_options(15, start: 1, end: 31, step: 2)
-
# => "<option value="1">1</option>
-
# <option value="3">3</option>
-
# <option value="5">5</option>..."
-
1
def build_options(selected, options = {})
-
364
options = {
-
leading_zeros: true, ampm: false, use_two_digit_numbers: false
-
}.merge!(options)
-
-
364
start = options.delete(:start) || 0
-
364
stop = options.delete(:end) || 59
-
364
step = options.delete(:step) || 1
-
364
leading_zeros = options.delete(:leading_zeros)
-
-
364
select_options = []
-
364
start.step(stop, step) do |i|
-
12140
value = leading_zeros ? sprintf("%02d", i) : i
-
12140
tag_options = { :value => value }
-
12140
tag_options[:selected] = "selected" if selected == i
-
12140
text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
-
12140
text = options[:ampm] ? AMPM_TRANSLATION[i] : text
-
12140
select_options << content_tag(:option, text, tag_options)
-
end
-
-
364
(select_options.join("\n") + "\n").html_safe
-
end
-
-
# Builds select tag from date type and html select options.
-
# build_select(:month, "<option value="1">January</option>...")
-
# => "<select id="post_written_on_2i" name="post[written_on(2i)]">
-
# <option value="1">January</option>...
-
# </select>"
-
1
def build_select(type, select_options_as_html)
-
480
select_options = {
-
:id => input_id_from_type(type),
-
:name => input_name_from_type(type)
-
}.merge!(@html_options)
-
480
select_options[:disabled] = 'disabled' if @options[:disabled]
-
480
select_options[:class] = type if @options[:with_css_classes]
-
-
480
select_html = "\n"
-
480
select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
-
480
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
-
480
select_html << select_options_as_html
-
-
480
(content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe
-
end
-
-
# Builds a prompt option tag with supplied options or from default options.
-
# prompt_option_tag(:month, prompt: 'Select month')
-
# => "<option value="">Select month</option>"
-
1
def prompt_option_tag(type, options)
-
60
prompt = case options
-
when Hash
-
23
default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false}
-
23
default_options.merge!(options)[type.to_sym]
-
when String
-
7
options
-
else
-
30
I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale])
-
end
-
-
60
prompt ? content_tag(:option, prompt, :value => '') : ''
-
end
-
-
# Builds hidden input tag for date part and value.
-
# build_hidden(:year, 2008)
-
# => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
-
1
def build_hidden(type, value)
-
138
select_options = {
-
:type => "hidden",
-
:id => input_id_from_type(type),
-
:name => input_name_from_type(type),
-
:value => value
-
}.merge!(@html_options.slice(:disabled))
-
138
select_options[:disabled] = 'disabled' if @options[:disabled]
-
-
138
tag(:input, select_options) + "\n".html_safe
-
end
-
-
# Returns the name attribute for the input tag.
-
# => post[written_on(1i)]
-
1
def input_name_from_type(type)
-
1236
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
-
1236
prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
-
-
1236
field_name = @options[:field_name] || type
-
1236
if @options[:include_position]
-
582
field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)"
-
end
-
-
1236
@options[:discard_type] ? prefix : "#{prefix}[#{field_name}]"
-
end
-
-
# Returns the id attribute for the input tag.
-
# => "post_written_on_1i"
-
1
def input_id_from_type(type)
-
618
id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
-
618
id = @options[:namespace] + '_' + id if @options[:namespace]
-
-
618
id
-
end
-
-
# Given an ordering of datetime components, create the selection HTML
-
# and join them with their appropriate separators.
-
1
def build_selects_from_types(order)
-
128
select = ''
-
354
first_visible = order.find { |type| !@options[:"discard_#{type}"] }
-
128
order.reverse.each do |type|
-
564
separator = separator(type) unless type == first_visible # don't add before first visible field
-
564
select.insert(0, separator.to_s + send("select_#{type}").to_s)
-
end
-
126
select.html_safe
-
end
-
-
# Returns the separator for a given datetime component.
-
1
def separator(type)
-
439
return "" if @options[:use_hidden]
-
-
417
case type
-
when :year, :month, :day
-
266
@options[:"discard_#{type}"] ? "" : @options[:date_separator]
-
when :hour
-
37
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
-
when :minute, :second
-
114
@options[:"discard_#{type}"] ? "" : @options[:time_separator]
-
end
-
end
-
end
-
-
1
class FormBuilder
-
1
def date_select(method, options = {}, html_options = {})
-
5
@template.date_select(@object_name, method, objectify_options(options), html_options)
-
end
-
-
1
def time_select(method, options = {}, html_options = {})
-
1
@template.time_select(@object_name, method, objectify_options(options), html_options)
-
end
-
-
1
def datetime_select(method, options = {}, html_options = {})
-
2
@template.datetime_select(@object_name, method, objectify_options(options), html_options)
-
end
-
end
-
end
-
end
-
1
module ActionView
-
# = Action View Debug Helper
-
#
-
# Provides a set of methods for making it easier to debug Rails objects.
-
1
module Helpers
-
1
module DebugHelper
-
-
1
include TagHelper
-
-
# Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
-
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
-
# Useful for inspecting an object at the time of rendering.
-
#
-
# @user = User.new({ username: 'testing', password: 'xyz', age: 42}) %>
-
# debug(@user)
-
# # =>
-
# <pre class='debug_dump'>--- !ruby/object:User
-
# attributes:
-
# updated_at:
-
# username: testing
-
#
-
# age: 42
-
# password: xyz
-
# created_at:
-
# attributes_cache: {}
-
#
-
# new_record: true
-
# </pre>
-
1
def debug(object)
-
begin
-
Marshal::dump(object)
-
object = ERB::Util.html_escape(object.to_yaml).gsub(" ", " ").html_safe
-
content_tag(:pre, object, :class => "debug_dump")
-
rescue Exception # errors from Marshal or YAML
-
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
-
content_tag(:code, object.to_yaml, :class => "debug_dump")
-
end
-
end
-
end
-
end
-
end
-
1
require 'cgi'
-
1
require 'action_view/helpers/date_helper'
-
1
require 'action_view/helpers/tag_helper'
-
1
require 'action_view/helpers/form_tag_helper'
-
1
require 'action_view/helpers/active_model_helper'
-
1
require 'action_view/helpers/tags'
-
1
require 'action_view/model_naming'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/string/output_safety'
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
module ActionView
-
# = Action View Form Helpers
-
1
module Helpers
-
# Form helpers are designed to make working with resources much easier
-
# compared to using vanilla HTML.
-
#
-
# Typically, a form designed to create or update a resource reflects the
-
# identity of the resource in several ways: (i) the url that the form is
-
# sent to (the form element's +action+ attribute) should result in a request
-
# being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
-
# parameter in the case of an existing resource), (ii) input fields should
-
# be named in such a way that in the controller their values appear in the
-
# appropriate places within the +params+ hash, and (iii) for an existing record,
-
# when the form is initially displayed, input fields corresponding to attributes
-
# of the resource should show the current values of those attributes.
-
#
-
# In Rails, this is usually achieved by creating the form using +form_for+ and
-
# a number of related helper methods. +form_for+ generates an appropriate <tt>form</tt>
-
# tag and yields a form builder object that knows the model the form is about.
-
# Input fields are created by calling methods defined on the form builder, which
-
# means they are able to generate the appropriate names and default values
-
# corresponding to the model attributes, as well as convenient IDs, etc.
-
# Conventions in the generated field names allow controllers to receive form data
-
# nicely structured in +params+ with no effort on your side.
-
#
-
# For example, to create a new person you typically set up a new instance of
-
# +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and
-
# in the view template pass that object to +form_for+:
-
#
-
# <%= form_for @person do |f| %>
-
# <%= f.label :first_name %>:
-
# <%= f.text_field :first_name %><br />
-
#
-
# <%= f.label :last_name %>:
-
# <%= f.text_field :last_name %><br />
-
#
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# The HTML generated for this would be (modulus formatting):
-
#
-
# <form action="/people" class="new_person" id="new_person" method="post">
-
# <div style="margin:0;padding:0;display:inline">
-
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
-
# </div>
-
# <label for="person_first_name">First name</label>:
-
# <input id="person_first_name" name="person[first_name]" type="text" /><br />
-
#
-
# <label for="person_last_name">Last name</label>:
-
# <input id="person_last_name" name="person[last_name]" type="text" /><br />
-
#
-
# <input name="commit" type="submit" value="Create Person" />
-
# </form>
-
#
-
# As you see, the HTML reflects knowledge about the resource in several spots,
-
# like the path the form should be submitted to, or the names of the input fields.
-
#
-
# In particular, thanks to the conventions followed in the generated field names, the
-
# controller gets a nested hash <tt>params[:person]</tt> with the person attributes
-
# set in the form. That hash is ready to be passed to <tt>Person.create</tt>:
-
#
-
# if @person = Person.create(params[:person])
-
# # success
-
# else
-
# # error handling
-
# end
-
#
-
# Interestingly, the exact same view code in the previous example can be used to edit
-
# a person. If <tt>@person</tt> is an existing record with name "John Smith" and ID 256,
-
# the code above as is would yield instead:
-
#
-
# <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
-
# <div style="margin:0;padding:0;display:inline">
-
# <input name="_method" type="hidden" value="put" />
-
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
-
# </div>
-
# <label for="person_first_name">First name</label>:
-
# <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />
-
#
-
# <label for="person_last_name">Last name</label>:
-
# <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br />
-
#
-
# <input name="commit" type="submit" value="Update Person" />
-
# </form>
-
#
-
# Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>.
-
# That works that way because the involved helpers know whether the resource is a new record or not,
-
# and generate HTML accordingly.
-
#
-
# The controller would receive the form data again in <tt>params[:person]</tt>, ready to be
-
# passed to <tt>Person#update_attributes</tt>:
-
#
-
# if @person.update_attributes(params[:person])
-
# # success
-
# else
-
# # error handling
-
# end
-
#
-
# That's how you typically work with resources.
-
1
module FormHelper
-
1
extend ActiveSupport::Concern
-
-
1
include FormTagHelper
-
1
include UrlHelper
-
1
include ModelNaming
-
-
# Creates a form that allows the user to create or update the attributes
-
# of a specific model object.
-
#
-
# The method can be used in several slightly different ways, depending on
-
# how much you wish to rely on Rails to infer automatically from the model
-
# how the form should be constructed. For a generic model object, a form
-
# can be created by passing +form_for+ a string or symbol representing
-
# the object we are concerned with:
-
#
-
# <%= form_for :person do |f| %>
-
# First name: <%= f.text_field :first_name %><br />
-
# Last name : <%= f.text_field :last_name %><br />
-
# Biography : <%= f.text_area :biography %><br />
-
# Admin? : <%= f.check_box :admin %><br />
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# The variable +f+ yielded to the block is a FormBuilder object that
-
# incorporates the knowledge about the model object represented by
-
# <tt>:person</tt> passed to +form_for+. Methods defined on the FormBuilder
-
# are used to generate fields bound to this model. Thus, for example,
-
#
-
# <%= f.text_field :first_name %>
-
#
-
# will get expanded to
-
#
-
# <%= text_field :person, :first_name %>
-
# which results in an html <tt><input></tt> tag whose +name+ attribute is
-
# <tt>person[first_name]</tt>. This means that when the form is submitted,
-
# the value entered by the user will be available in the controller as
-
# <tt>params[:person][:first_name]</tt>.
-
#
-
# For fields generated in this way using the FormBuilder,
-
# if <tt>:person</tt> also happens to be the name of an instance variable
-
# <tt>@person</tt>, the default value of the field shown when the form is
-
# initially displayed (e.g. in the situation where you are editing an
-
# existing record) will be the value of the corresponding attribute of
-
# <tt>@person</tt>.
-
#
-
# The rightmost argument to +form_for+ is an
-
# optional hash of options -
-
#
-
# * <tt>:url</tt> - The URL the form is to be submitted to. This may be
-
# represented in the same way as values passed to +url_for+ or +link_to+.
-
# So for example you may use a named route directly. When the model is
-
# represented by a string or symbol, as in the example above, if the
-
# <tt>:url</tt> option is not specified, by default the form will be
-
# sent back to the current url (We will describe below an alternative
-
# resource-oriented usage of +form_for+ in which the URL does not need
-
# to be specified explicitly).
-
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
-
# id attributes on form elements. The namespace attribute will be prefixed
-
# with underscore on the generated HTML id.
-
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
-
#
-
# Also note that +form_for+ doesn't create an exclusive scope. It's still
-
# possible to use both the stand-alone FormHelper methods and methods
-
# from FormTagHelper. For example:
-
#
-
# <%= form_for :person do |f| %>
-
# First name: <%= f.text_field :first_name %>
-
# Last name : <%= f.text_field :last_name %>
-
# Biography : <%= text_area :person, :biography %>
-
# Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# This also works for the methods in FormOptionHelper and DateHelper that
-
# are designed to work with an object as base, like
-
# FormOptionHelper#collection_select and DateHelper#datetime_select.
-
#
-
# === #form_for with a model object
-
#
-
# In the examples above, the object to be created or edited was
-
# represented by a symbol passed to +form_for+, and we noted that
-
# a string can also be used equivalently. It is also possible, however,
-
# to pass a model object itself to +form_for+. For example, if <tt>@post</tt>
-
# is an existing record you wish to edit, you can create the form using
-
#
-
# <%= form_for @post do |f| %>
-
# ...
-
# <% end %>
-
#
-
# This behaves in almost the same way as outlined previously, with a
-
# couple of small exceptions. First, the prefix used to name the input
-
# elements within the form (hence the key that denotes them in the +params+
-
# hash) is actually derived from the object's _class_, e.g. <tt>params[:post]</tt>
-
# if the object's class is +Post+. However, this can be overwritten using
-
# the <tt>:as</tt> option, e.g. -
-
#
-
# <%= form_for(@person, as: :client) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# would result in <tt>params[:client]</tt>.
-
#
-
# Secondly, the field values shown when the form is initially displayed
-
# are taken from the attributes of the object passed to +form_for+,
-
# regardless of whether the object is an instance
-
# variable. So, for example, if we had a _local_ variable +post+
-
# representing an existing record,
-
#
-
# <%= form_for post do |f| %>
-
# ...
-
# <% end %>
-
#
-
# would produce a form with fields whose initial state reflect the current
-
# values of the attributes of +post+.
-
#
-
# === Resource-oriented style
-
#
-
# In the examples just shown, although not indicated explicitly, we still
-
# need to use the <tt>:url</tt> option in order to specify where the
-
# form is going to be sent. However, further simplification is possible
-
# if the record passed to +form_for+ is a _resource_, i.e. it corresponds
-
# to a set of RESTful routes, e.g. defined using the +resources+ method
-
# in <tt>config/routes.rb</tt>. In this case Rails will simply infer the
-
# appropriate URL from the record itself. For example,
-
#
-
# <%= form_for @post do |f| %>
-
# ...
-
# <% end %>
-
#
-
# is then equivalent to something like:
-
#
-
# <%= form_for @post, as: :post, url: post_path(@post), method: :put, html: { class: "edit_post", id: "edit_post_45" } do |f| %>
-
# ...
-
# <% end %>
-
#
-
# And for a new record
-
#
-
# <%= form_for(Post.new) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# is equivalent to something like:
-
#
-
# <%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %>
-
# ...
-
# <% end %>
-
#
-
# However you can still overwrite individual conventions, such as:
-
#
-
# <%= form_for(@post, url: super_posts_path) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# You can also set the answer format, like this:
-
#
-
# <%= form_for(@post, format: :json) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# For namespaced routes, like +admin_post_url+:
-
#
-
# <%= form_for([:admin, @post]) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# If your resource has associations defined, for example, you want to add comments
-
# to the document given that the routes are set correctly:
-
#
-
# <%= form_for([@document, @comment]) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# Where <tt>@document = Document.find(params[:id])</tt> and
-
# <tt>@comment = Comment.new</tt>.
-
#
-
# === Setting the method
-
#
-
# You can force the form to use the full array of HTTP verbs by setting
-
#
-
# method: (:get|:post|:patch|:put|:delete)
-
#
-
# in the options hash. If the verb is not GET or POST, which are natively
-
# supported by HTML forms, the form will be set to POST and a hidden input
-
# called _method will carry the intended verb for the server to interpret.
-
#
-
# === Unobtrusive JavaScript
-
#
-
# Specifying:
-
#
-
# remote: true
-
#
-
# in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
-
# behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
-
# POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
-
# Even though it's using JavaScript to serialize the form elements, the form submission will work just like
-
# a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
-
#
-
# Example:
-
#
-
# <%= form_for(@post, remote: true) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# The HTML generated for this would be:
-
#
-
# <form action='http://www.example.com' method='post' data-remote='true'>
-
# <div style='margin:0;padding:0;display:inline'>
-
# <input name='_method' type='hidden' value='put' />
-
# </div>
-
# ...
-
# </form>
-
#
-
# === Setting HTML options
-
#
-
# You can set data attributes directly by passing in a data hash, but all other HTML options must be wrapped in
-
# the HTML key. Example:
-
#
-
# <%= form_for(@post, data: { behavior: "autosave" }, html: { name: "go" }) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# The HTML generated for this would be:
-
#
-
# <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
-
# <div style='margin:0;padding:0;display:inline'>
-
# <input name='_method' type='hidden' value='put' />
-
# </div>
-
# ...
-
# </form>
-
#
-
# === Removing hidden model id's
-
#
-
# The form_for method automatically includes the model id as a hidden field in the form.
-
# This is used to maintain the correlation between the form data and its associated model.
-
# Some ORM systems do not use IDs on nested models so in this case you want to be able
-
# to disable the hidden id.
-
#
-
# In the following example the Post model has many Comments stored within it in a NoSQL database,
-
# thus there is no primary key for comments.
-
#
-
# Example:
-
#
-
# <%= form_for(@post) do |f| %>
-
# <%= f.fields_for(:comments, include_id: false) do |cf| %>
-
# ...
-
# <% end %>
-
# <% end %>
-
#
-
# === Customized form builders
-
#
-
# You can also build forms using a customized FormBuilder class. Subclass
-
# FormBuilder and override or define some more helpers, then use your
-
# custom builder. For example, let's say you made a helper to
-
# automatically add labels to form inputs.
-
#
-
# <%= form_for @person, url: { action: "create" }, builder: LabellingFormBuilder do |f| %>
-
# <%= f.text_field :first_name %>
-
# <%= f.text_field :last_name %>
-
# <%= f.text_area :biography %>
-
# <%= f.check_box :admin %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# In this case, if you use this:
-
#
-
# <%= render f %>
-
#
-
# The rendered template is <tt>people/_labelling_form</tt> and the local
-
# variable referencing the form builder is called
-
# <tt>labelling_form</tt>.
-
#
-
# The custom FormBuilder class is automatically merged with the options
-
# of a nested fields_for call, unless it's explicitly set.
-
#
-
# In many cases you will want to wrap the above in another helper, so you
-
# could do something like the following:
-
#
-
# def labelled_form_for(record_or_name_or_array, *args, &proc)
-
# options = args.extract_options!
-
# form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &proc)
-
# end
-
#
-
# If you don't need to attach a form to a model instance, then check out
-
# FormTagHelper#form_tag.
-
#
-
# === Form to external resources
-
#
-
# When you build forms to external resources sometimes you need to set an authenticity token or just render a form
-
# without it, for example when you submit data to a payment gateway number and types of fields could be limited.
-
#
-
# To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
-
#
-
# <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f|
-
# ...
-
# <% end %>
-
#
-
# If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
-
#
-
# <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
-
# ...
-
# <% end %>
-
1
def form_for(record, options = {}, &proc)
-
108
raise ArgumentError, "Missing block" unless block_given?
-
-
108
options[:html] ||= {}
-
-
108
case record
-
when String, Symbol
-
15
object_name = record
-
15
object = nil
-
else
-
93
object = record.is_a?(Array) ? record.last : record
-
93
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
-
91
object_name = options[:as] || model_name_from_record_or_class(object).param_key
-
91
apply_form_for_options!(record, object, options)
-
end
-
-
106
options[:html][:data] = options.delete(:data) if options.has_key?(:data)
-
106
options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
-
106
options[:html][:method] = options.delete(:method) if options.has_key?(:method)
-
106
options[:html][:authenticity_token] = options.delete(:authenticity_token)
-
-
106
builder = options[:parent_builder] = instantiate_builder(object_name, object, options)
-
106
fields_for = fields_for(object_name, object, options, &proc)
-
106
default_options = builder.multipart? ? { multipart: true } : {}
-
106
default_options.merge!(options.delete(:html))
-
-
212
form_tag(options.delete(:url) || {}, default_options) { fields_for }
-
end
-
-
1
def apply_form_for_options!(record, object, options) #:nodoc:
-
91
object = convert_to_model(object)
-
-
91
as = options[:as]
-
91
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
-
91
options[:html].reverse_merge!(
-
class: as ? "#{action}_#{as}" : dom_class(object, action),
-
id: as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
-
method: method
-
)
-
-
91
options[:url] ||= polymorphic_path(record, format: options.delete(:format))
-
end
-
1
private :apply_form_for_options!
-
-
# Creates a scope around a specific model object like form_for, but
-
# doesn't create the form tags themselves. This makes fields_for suitable
-
# for specifying additional model objects in the same form.
-
#
-
# === Generic Examples
-
#
-
# Although the usage and purpose of +field_for+ is similar to +form_for+'s,
-
# its method signature is slightly different. Like +form_for+, it yields
-
# a FormBuilder object associated with a particular model object to a block,
-
# and within the block allows methods to be called on the builder to
-
# generate fields associated with the model object. Fields may reflect
-
# a model object in two ways - how they are named (hence how submitted
-
# values appear within the +params+ hash in the controller) and what
-
# default values are shown when the form the fields appear in is first
-
# displayed. In order for both of these features to be specified independently,
-
# both an object name (represented by either a symbol or string) and the
-
# object itself can be passed to the method separately -
-
#
-
# <%= form_for @person do |person_form| %>
-
# First name: <%= person_form.text_field :first_name %>
-
# Last name : <%= person_form.text_field :last_name %>
-
#
-
# <%= fields_for :permission, @person.permission do |permission_fields| %>
-
# Admin? : <%= permission_fields.check_box :admin %>
-
# <% end %>
-
#
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# In this case, the checkbox field will be represented by an HTML +input+
-
# tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted
-
# value will appear in the controller as <tt>params[:permission][:admin]</tt>.
-
# If <tt>@person.permission</tt> is an existing record with an attribute
-
# +admin+, the initial state of the checkbox when first displayed will
-
# reflect the value of <tt>@person.permission.admin</tt>.
-
#
-
# Often this can be simplified by passing just the name of the model
-
# object to +fields_for+ -
-
#
-
# <%= fields_for :permission do |permission_fields| %>
-
# Admin?: <%= permission_fields.check_box :admin %>
-
# <% end %>
-
#
-
# ...in which case, if <tt>:permission</tt> also happens to be the name of an
-
# instance variable <tt>@permission</tt>, the initial state of the input
-
# field will reflect the value of that variable's attribute <tt>@permission.admin</tt>.
-
#
-
# Alternatively, you can pass just the model object itself (if the first
-
# argument isn't a string or symbol +fields_for+ will realize that the
-
# name has been omitted) -
-
#
-
# <%= fields_for @person.permission do |permission_fields| %>
-
# Admin?: <%= permission_fields.check_box :admin %>
-
# <% end %>
-
#
-
# and +fields_for+ will derive the required name of the field from the
-
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
-
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
-
#
-
# Note: This also works for the methods in FormOptionHelper and
-
# DateHelper that are designed to work with an object as base, like
-
# FormOptionHelper#collection_select and DateHelper#datetime_select.
-
#
-
# === Nested Attributes Examples
-
#
-
# When the object belonging to the current scope has a nested attribute
-
# writer for a certain attribute, fields_for will yield a new scope
-
# for that attribute. This allows you to create forms that set or change
-
# the attributes of a parent object and its associations in one go.
-
#
-
# Nested attribute writers are normal setter methods named after an
-
# association. The most common way of defining these writers is either
-
# with +accepts_nested_attributes_for+ in a model definition or by
-
# defining a method with the proper name. For example: the attribute
-
# writer for the association <tt>:address</tt> is called
-
# <tt>address_attributes=</tt>.
-
#
-
# Whether a one-to-one or one-to-many style form builder will be yielded
-
# depends on whether the normal reader method returns a _single_ object
-
# or an _array_ of objects.
-
#
-
# ==== One-to-one
-
#
-
# Consider a Person class which returns a _single_ Address from the
-
# <tt>address</tt> reader method and responds to the
-
# <tt>address_attributes=</tt> writer method:
-
#
-
# class Person
-
# def address
-
# @address
-
# end
-
#
-
# def address_attributes=(attributes)
-
# # Process the attributes hash
-
# end
-
# end
-
#
-
# This model can now be used with a nested fields_for, like so:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :address do |address_fields| %>
-
# Street : <%= address_fields.text_field :street %>
-
# Zip code: <%= address_fields.text_field :zip_code %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# When address is already an association on a Person you can use
-
# +accepts_nested_attributes_for+ to define the writer method for you:
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :address
-
# accepts_nested_attributes_for :address
-
# end
-
#
-
# If you want to destroy the associated model through the form, you have
-
# to enable it first using the <tt>:allow_destroy</tt> option for
-
# +accepts_nested_attributes_for+:
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :address
-
# accepts_nested_attributes_for :address, allow_destroy: true
-
# end
-
#
-
# Now, when you use a form element with the <tt>_destroy</tt> parameter,
-
# with a value that evaluates to +true+, you will destroy the associated
-
# model (eg. 1, '1', true, or 'true'):
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :address do |address_fields| %>
-
# ...
-
# Delete: <%= address_fields.check_box :_destroy %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# ==== One-to-many
-
#
-
# Consider a Person class which returns an _array_ of Project instances
-
# from the <tt>projects</tt> reader method and responds to the
-
# <tt>projects_attributes=</tt> writer method:
-
#
-
# class Person
-
# def projects
-
# [@project1, @project2]
-
# end
-
#
-
# def projects_attributes=(attributes)
-
# # Process the attributes hash
-
# end
-
# end
-
#
-
# Note that the <tt>projects_attributes=</tt> writer method is in fact
-
# required for fields_for to correctly identify <tt>:projects</tt> as a
-
# collection, and the correct indices to be set in the form markup.
-
#
-
# When projects is already an association on Person you can use
-
# +accepts_nested_attributes_for+ to define the writer method for you:
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :projects
-
# accepts_nested_attributes_for :projects
-
# end
-
#
-
# This model can now be used with a nested fields_for. The block given to
-
# the nested fields_for call will be repeated for each instance in the
-
# collection:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects do |project_fields| %>
-
# <% if project_fields.object.active? %>
-
# Name: <%= project_fields.text_field :name %>
-
# <% end %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# It's also possible to specify the instance to be used:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <% @person.projects.each do |project| %>
-
# <% if project.active? %>
-
# <%= person_form.fields_for :projects, project do |project_fields| %>
-
# Name: <%= project_fields.text_field :name %>
-
# <% end %>
-
# <% end %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# Or a collection to be used:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
-
# Name: <%= project_fields.text_field :name %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# When projects is already an association on Person you can use
-
# +accepts_nested_attributes_for+ to define the writer method for you:
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :projects
-
# accepts_nested_attributes_for :projects
-
# end
-
#
-
# If you want to destroy any of the associated models through the
-
# form, you have to enable it first using the <tt>:allow_destroy</tt>
-
# option for +accepts_nested_attributes_for+:
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :projects
-
# accepts_nested_attributes_for :projects, allow_destroy: true
-
# end
-
#
-
# This will allow you to specify which models to destroy in the
-
# attributes hash by adding a form element for the <tt>_destroy</tt>
-
# parameter with a value that evaluates to +true+
-
# (eg. 1, '1', true, or 'true'):
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects do |project_fields| %>
-
# Delete: <%= project_fields.check_box :_destroy %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# When a collection is used you might want to know the index of each
-
# object into the array. For this purpose, the <tt>index</tt> method
-
# is available in the FormBuilder object.
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects do |project_fields| %>
-
# Project #<%= project_fields.index %>
-
# ...
-
# <% end %>
-
# ...
-
# <% end %>
-
1
def fields_for(record_name, record_object = nil, options = {}, &block)
-
208
builder = instantiate_builder(record_name, record_object, options)
-
208
output = capture(builder, &block)
-
208
output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
-
208
output
-
end
-
-
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
-
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
-
# Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
-
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
-
# target labels for radio_button tags (where the value is used in the ID of the input tag).
-
#
-
# ==== Examples
-
# label(:post, :title)
-
# # => <label for="post_title">Title</label>
-
#
-
# You can localize your labels based on model and attribute names.
-
# For example you can define the following in your locale (e.g. en.yml)
-
#
-
# helpers:
-
# label:
-
# post:
-
# body: "Write your entire text here"
-
#
-
# Which then will result in
-
#
-
# label(:post, :body)
-
# # => <label for="post_body">Write your entire text here</label>
-
#
-
# Localization can also be based purely on the translation of the attribute-name
-
# (if you are using ActiveRecord):
-
#
-
# activerecord:
-
# attributes:
-
# post:
-
# cost: "Total cost"
-
#
-
# label(:post, :cost)
-
# # => <label for="post_cost">Total cost</label>
-
#
-
# label(:post, :title, "A short title")
-
# # => <label for="post_title">A short title</label>
-
#
-
# label(:post, :title, "A short title", class: "title_label")
-
# # => <label for="post_title" class="title_label">A short title</label>
-
#
-
# label(:post, :privacy, "Public Post", value: "public")
-
# # => <label for="post_privacy_public">Public Post</label>
-
#
-
# label(:post, :terms) do
-
# 'Accept <a href="/terms">Terms</a>.'.html_safe
-
# end
-
1
def label(object_name, method, content_or_options = nil, options = nil, &block)
-
125
Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
-
end
-
-
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown.
-
#
-
# ==== Examples
-
# text_field(:post, :title, size: 20)
-
# # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
-
#
-
# text_field(:post, :title, class: "create_input")
-
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
-
#
-
# text_field(:session, :user, onchange: "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
-
# # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/>
-
#
-
# text_field(:snippet, :code, size: 20, class: 'code_input')
-
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
-
1
def text_field(object_name, method, options = {})
-
141
Tags::TextField.new(object_name, method, self, options).render
-
end
-
-
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown. For security reasons this field is blank by default; pass in a value via +options+ if this is not desired.
-
#
-
# ==== Examples
-
# password_field(:login, :pass, size: 20)
-
# # => <input type="password" id="login_pass" name="login[pass]" size="20" />
-
#
-
# password_field(:account, :secret, class: "form_input", value: @account.secret)
-
# # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
-
#
-
# password_field(:user, :password, onchange: "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
-
# # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
-
#
-
# password_field(:account, :pin, size: 20, class: 'form_input')
-
# # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
-
1
def password_field(object_name, method, options = {})
-
3
Tags::PasswordField.new(object_name, method, self, options).render
-
end
-
-
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown.
-
#
-
# ==== Examples
-
# hidden_field(:signup, :pass_confirm)
-
# # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
-
#
-
# hidden_field(:post, :tag_list)
-
# # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
-
#
-
# hidden_field(:user, :token)
-
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
-
1
def hidden_field(object_name, method, options = {})
-
33
Tags::HiddenField.new(object_name, method, self, options).render
-
end
-
-
# Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown.
-
#
-
# Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
-
#
-
# ==== Examples
-
# file_field(:user, :avatar)
-
# # => <input type="file" id="user_avatar" name="user[avatar]" />
-
#
-
# file_field(:post, :attached, accept: 'text/html')
-
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
-
#
-
# file_field(:attachment, :file, class: 'file_input')
-
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
-
1
def file_field(object_name, method, options = {})
-
3
Tags::FileField.new(object_name, method, self, options).render
-
end
-
-
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
-
# on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+.
-
#
-
# ==== Examples
-
# text_area(:post, :body, cols: 20, rows: 40)
-
# # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
-
# # #{@post.body}
-
# # </textarea>
-
#
-
# text_area(:comment, :text, size: "20x30")
-
# # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
-
# # #{@comment.text}
-
# # </textarea>
-
#
-
# text_area(:application, :notes, cols: 40, rows: 15, class: 'app_input')
-
# # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
-
# # #{@application.notes}
-
# # </textarea>
-
#
-
# text_area(:entry, :body, size: "20x20", disabled: 'disabled')
-
# # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
-
# # #{@entry.body}
-
# # </textarea>
-
1
def text_area(object_name, method, options = {})
-
46
Tags::TextArea.new(object_name, method, self, options).render
-
end
-
-
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
-
# It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
-
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
-
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
-
#
-
# ==== Gotcha
-
#
-
# The HTML specification says unchecked check boxes are not successful, and
-
# thus web browsers do not send them. Unfortunately this introduces a gotcha:
-
# if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
-
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
-
# any mass-assignment idiom like
-
#
-
# @invoice.update_attributes(params[:invoice])
-
#
-
# wouldn't update the flag.
-
#
-
# To prevent this the helper generates an auxiliary hidden field before
-
# the very check box. The hidden field has the same name and its
-
# attributes mimic an unchecked check box.
-
#
-
# This way, the client either sends only the hidden field (representing
-
# the check box is unchecked), or both fields. Since the HTML specification
-
# says key/value pairs have to be sent in the same order they appear in the
-
# form, and parameters extraction gets the last occurrence of any repeated
-
# key in the query string, that works for ordinary forms.
-
#
-
# Unfortunately that workaround does not work when the check box goes
-
# within an array-like parameter, as in
-
#
-
# <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %>
-
# <%= form.check_box :paid %>
-
# ...
-
# <% end %>
-
#
-
# because parameter name repetition is precisely what Rails seeks to distinguish
-
# the elements of the array. For each item with a checked check box you
-
# get an extra ghost item with only that attribute, assigned to "0".
-
#
-
# In that case it is preferable to either use +check_box_tag+ or to use
-
# hashes instead of arrays.
-
#
-
# # Let's say that @post.validated? is 1:
-
# check_box("post", "validated")
-
# # => <input name="post[validated]" type="hidden" value="0" />
-
# # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
-
#
-
# # Let's say that @puppy.gooddog is "no":
-
# check_box("puppy", "gooddog", {}, "yes", "no")
-
# # => <input name="puppy[gooddog]" type="hidden" value="no" />
-
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
-
#
-
# check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
-
# # => <input name="eula[accepted]" type="hidden" value="no" />
-
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
-
1
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
-
119
Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
-
end
-
-
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
-
# radio button will be checked.
-
#
-
# To force the radio button to be checked pass <tt>checked: true</tt> in the
-
# +options+ hash. You may pass HTML options there as well.
-
#
-
# # Let's say that @post.category returns "rails":
-
# radio_button("post", "category", "rails")
-
# radio_button("post", "category", "java")
-
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
-
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
-
#
-
# radio_button("user", "receive_newsletter", "yes")
-
# radio_button("user", "receive_newsletter", "no")
-
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
-
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
-
1
def radio_button(object_name, method, tag_value, options = {})
-
49
Tags::RadioButton.new(object_name, method, self, tag_value, options).render
-
end
-
-
# Returns a text_field of type "color".
-
#
-
# color_field("car", "color")
-
# # => <input id="car_color" name="car[color]" type="color" value="#000000" />
-
1
def color_field(object_name, method, options = {})
-
2
Tags::ColorField.new(object_name, method, self, options).render
-
end
-
-
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
-
# some browsers.
-
#
-
# search_field(:user, :name)
-
# # => <input id="user_name" name="user[name]" type="search" />
-
# search_field(:user, :name, autosave: false)
-
# # => <input autosave="false" id="user_name" name="user[name]" type="search" />
-
# search_field(:user, :name, results: 3)
-
# # => <input id="user_name" name="user[name]" results="3" type="search" />
-
# # Assume request.host returns "www.example.com"
-
# search_field(:user, :name, autosave: true)
-
# # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" />
-
# search_field(:user, :name, onsearch: true)
-
# # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
-
# search_field(:user, :name, autosave: false, onsearch: true)
-
# # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
-
# search_field(:user, :name, autosave: true, onsearch: true)
-
# # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" />
-
1
def search_field(object_name, method, options = {})
-
2
Tags::SearchField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "tel".
-
#
-
# telephone_field("user", "phone")
-
# # => <input id="user_phone" name="user[phone]" type="tel" />
-
#
-
1
def telephone_field(object_name, method, options = {})
-
1
Tags::TelField.new(object_name, method, self, options).render
-
end
-
# aliases telephone_field
-
1
alias phone_field telephone_field
-
-
# Returns a text_field of type "date".
-
#
-
# date_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="date" />
-
#
-
# The default value is generated by trying to call "to_date"
-
# on the object's value, which makes it behave as expected for instances
-
# of DateTime and ActiveSupport::TimeWithZone. You can still override that
-
# by passing the "value" option explicitly, e.g.
-
#
-
# @user.born_on = Date.new(1984, 1, 27)
-
# date_field("user", "born_on", value: "1984-05-12")
-
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
-
#
-
1
def date_field(object_name, method, options = {})
-
5
Tags::DateField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "time".
-
#
-
# The default value is generated by trying to call +strftime+ with "%T.%L"
-
# on the objects's value. It is still possible to override that
-
# by passing the "value" option.
-
#
-
# === Options
-
# * Accepts same options as time_field_tag
-
#
-
# === Example
-
# time_field("task", "started_at")
-
# # => <input id="task_started_at" name="task[started_at]" type="time" />
-
#
-
1
def time_field(object_name, method, options = {})
-
5
Tags::TimeField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "datetime".
-
#
-
# datetime_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="datetime" />
-
#
-
# The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
-
# on the object's value, which makes it behave as expected for instances
-
# of DateTime and ActiveSupport::TimeWithZone.
-
#
-
# @user.born_on = Date.new(1984, 1, 12)
-
# datetime_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
-
#
-
1
def datetime_field(object_name, method, options = {})
-
5
Tags::DatetimeField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "datetime-local".
-
#
-
# datetime_local_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
-
#
-
# The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
-
# on the object's value, which makes it behave as expected for instances
-
# of DateTime and ActiveSupport::TimeWithZone.
-
#
-
# @user.born_on = Date.new(1984, 1, 12)
-
# datetime_local_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
-
#
-
1
def datetime_local_field(object_name, method, options = {})
-
5
Tags::DatetimeLocalField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "month".
-
#
-
# month_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="month" />
-
#
-
# The default value is generated by trying to call +strftime+ with "%Y-%m"
-
# on the object's value, which makes it behave as expected for instances
-
# of DateTime and ActiveSupport::TimeWithZone.
-
#
-
# @user.born_on = Date.new(1984, 1, 27)
-
# month_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" />
-
#
-
1
def month_field(object_name, method, options = {})
-
5
Tags::MonthField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "week".
-
#
-
# week_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="week" />
-
#
-
# The default value is generated by trying to call +strftime+ with "%Y-W%W"
-
# on the object's value, which makes it behave as expected for instances
-
# of DateTime and ActiveSupport::TimeWithZone.
-
#
-
# @user.born_on = Date.new(1984, 5, 12)
-
# week_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" />
-
#
-
1
def week_field(object_name, method, options = {})
-
5
Tags::WeekField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "url".
-
#
-
# url_field("user", "homepage")
-
# # => <input id="user_homepage" name="user[homepage]" type="url" />
-
#
-
1
def url_field(object_name, method, options = {})
-
1
Tags::UrlField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "email".
-
#
-
# email_field("user", "address")
-
# # => <input id="user_address" name="user[address]" type="email" />
-
#
-
1
def email_field(object_name, method, options = {})
-
1
Tags::EmailField.new(object_name, method, self, options).render
-
end
-
-
# Returns an input tag of type "number".
-
#
-
# ==== Options
-
# * Accepts same options as number_field_tag
-
1
def number_field(object_name, method, options = {})
-
2
Tags::NumberField.new(object_name, method, self, options).render
-
end
-
-
# Returns an input tag of type "range".
-
#
-
# ==== Options
-
# * Accepts same options as range_field_tag
-
1
def range_field(object_name, method, options = {})
-
2
Tags::RangeField.new(object_name, method, self, options).render
-
end
-
-
1
private
-
-
1
def instantiate_builder(record_name, record_object, options)
-
314
case record_name
-
when String, Symbol
-
312
object = record_object
-
312
object_name = record_name
-
else
-
2
object = record_name
-
2
object_name = model_name_from_record_or_class(object).param_key
-
end
-
-
314
builder = options[:builder] || default_form_builder
-
314
builder.new(object_name, object, self, options)
-
end
-
-
1
def default_form_builder
-
276
builder = ActionView::Base.default_form_builder
-
276
builder.respond_to?(:constantize) ? builder.constantize : builder
-
end
-
end
-
-
1
class FormBuilder
-
1
include ModelNaming
-
-
# The methods which wrap a form helper call.
-
1
class_attribute :field_helpers
-
1
self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model, :model_name_from_record_or_class]
-
-
1
attr_accessor :object_name, :object, :options
-
-
1
attr_reader :multipart, :parent_builder, :index
-
1
alias :multipart? :multipart
-
-
1
def multipart=(multipart)
-
5
@multipart = multipart
-
5
parent_builder.multipart = multipart if parent_builder
-
end
-
-
1
def self._to_partial_path
-
4
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
-
end
-
-
1
def to_partial_path
-
4
self.class._to_partial_path
-
end
-
-
1
def to_model
-
3
self
-
end
-
-
1
def initialize(object_name, object, template, options, block=nil)
-
318
if block
-
1
ActiveSupport::Deprecation.warn "Giving a block to FormBuilder is deprecated and has no effect anymore."
-
end
-
-
318
@nested_child_index = {}
-
318
@object_name, @object, @template, @options = object_name, object, template, options
-
318
@parent_builder = options[:parent_builder]
-
318
@default_options = @options ? @options.slice(:index, :namespace) : {}
-
318
if @object_name.to_s.match(/\[\]$/)
-
18
if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
-
18
@auto_index = object.to_param
-
else
-
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
-
end
-
end
-
318
@multipart = nil
-
318
@index = options[:index] || options[:child_index]
-
end
-
-
1
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
-
17
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
-
def #{selector}(method, options = {}) # def text_field(method, options = {})
-
@template.send( # @template.send(
-
#{selector.inspect}, # "text_field",
-
@object_name, # @object_name,
-
method, # method,
-
objectify_options(options)) # objectify_options(options))
-
end # end
-
RUBY_EVAL
-
end
-
-
1
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
-
65
fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
-
65
fields_options[:builder] ||= options[:builder]
-
65
fields_options[:parent_builder] = self
-
65
fields_options[:namespace] = options[:namespace]
-
-
65
case record_name
-
when String, Symbol
-
62
if nested_attributes_association?(record_name)
-
51
return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
-
end
-
else
-
3
record_object = record_name.is_a?(Array) ? record_name.last : record_name
-
3
record_name = model_name_from_record_or_class(record_object).param_key
-
end
-
-
14
index = if options.has_key?(:index)
-
4
options[:index]
-
elsif defined?(@auto_index)
-
4
self.object_name = @object_name.to_s.sub(/\[\]$/,"")
-
4
@auto_index
-
end
-
-
14
record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
-
14
fields_options[:child_index] = index
-
-
14
@template.fields_for(record_name, record_object, fields_options, &block)
-
end
-
-
1
def label(method, text = nil, options = {}, &block)
-
18
@template.label(@object_name, method, text, objectify_options(options), &block)
-
end
-
-
1
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
-
21
@template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
-
end
-
-
1
def radio_button(method, tag_value, options = {})
-
1
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
-
end
-
-
1
def hidden_field(method, options = {})
-
27
@emitted_hidden_id = true if method == :id
-
27
@template.hidden_field(@object_name, method, objectify_options(options))
-
end
-
-
1
def file_field(method, options = {})
-
2
self.multipart = true
-
2
@template.file_field(@object_name, method, objectify_options(options))
-
end
-
-
# Add the submit button for the given form. When no value is given, it checks
-
# if the object is a new resource or not to create the proper label:
-
#
-
# <%= form_for @post do |f| %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# In the example above, if @post is a new record, it will use "Create Post" as
-
# submit button label, otherwise, it uses "Update Post".
-
#
-
# Those labels can be customized using I18n, under the helpers.submit key and accept
-
# the %{model} as translation interpolation:
-
#
-
# en:
-
# helpers:
-
# submit:
-
# create: "Create a %{model}"
-
# update: "Confirm changes to %{model}"
-
#
-
# It also searches for a key specific for the given object:
-
#
-
# en:
-
# helpers:
-
# submit:
-
# post:
-
# create: "Add %{model}"
-
#
-
1
def submit(value=nil, options={})
-
9
value, options = nil, value if value.is_a?(Hash)
-
9
value ||= submit_default_value
-
9
@template.submit_tag(value, options)
-
end
-
-
# Add the submit button for the given form. When no value is given, it checks
-
# if the object is a new resource or not to create the proper label:
-
#
-
# <%= form_for @post do |f| %>
-
# <%= f.button %>
-
# <% end %>
-
#
-
# In the example above, if @post is a new record, it will use "Create Post" as
-
# button label, otherwise, it uses "Update Post".
-
#
-
# Those labels can be customized using I18n, under the helpers.submit key
-
# (the same as submit helper) and accept the %{model} as translation interpolation:
-
#
-
# en:
-
# helpers:
-
# submit:
-
# create: "Create a %{model}"
-
# update: "Confirm changes to %{model}"
-
#
-
# It also searches for a key specific for the given object:
-
#
-
# en:
-
# helpers:
-
# submit:
-
# post:
-
# create: "Add %{model}"
-
#
-
# ==== Examples
-
# button("Create a post")
-
# # => <button name='button' type='submit'>Create post</button>
-
#
-
# button do
-
# content_tag(:strong, 'Ask me!')
-
# end
-
# # => <button name='button' type='submit'>
-
# # <strong>Ask me!</strong>
-
# # </button>
-
#
-
1
def button(value = nil, options = {}, &block)
-
2
value, options = nil, value if value.is_a?(Hash)
-
2
value ||= submit_default_value
-
2
@template.button_tag(value, options, &block)
-
end
-
-
1
def emitted_hidden_id?
-
27
@emitted_hidden_id ||= nil
-
end
-
-
1
private
-
1
def objectify_options(options)
-
226
@default_options.merge(options.merge(object: @object))
-
end
-
-
1
def submit_default_value
-
5
object = convert_to_model(@object)
-
5
key = object ? (object.persisted? ? :update : :create) : :submit
-
-
5
model = if object.class.respond_to?(:model_name)
-
4
object.class.model_name.human
-
else
-
1
@object_name.to_s.humanize
-
end
-
-
5
defaults = []
-
5
defaults << :"helpers.submit.#{object_name}.#{key}"
-
5
defaults << :"helpers.submit.#{key}"
-
5
defaults << "#{key.to_s.humanize} #{model}"
-
-
5
I18n.t(defaults.shift, model: model, default: defaults)
-
end
-
-
1
def nested_attributes_association?(association_name)
-
62
@object.respond_to?("#{association_name}_attributes=")
-
end
-
-
1
def fields_for_with_nested_attributes(association_name, association, options, block)
-
51
name = "#{object_name}[#{association_name}_attributes]"
-
51
association = convert_to_model(association)
-
-
51
if association.respond_to?(:persisted?)
-
33
association = [association] if @object.send(association_name).is_a?(Array)
-
elsif !association.respond_to?(:to_ary)
-
13
association = @object.send(association_name)
-
end
-
-
51
if association.respond_to?(:to_ary)
-
39
explicit_child_index = options[:child_index]
-
39
output = ActiveSupport::SafeBuffer.new
-
39
association.each do |child|
-
43
options[:child_index] = nested_child_index(name) unless explicit_child_index
-
43
output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
-
end
-
39
output
-
12
elsif association
-
12
fields_for_nested_model(name, association, options, block)
-
end
-
end
-
-
1
def fields_for_nested_model(name, object, options, block)
-
55
object = convert_to_model(object)
-
-
55
parent_include_id = self.options.fetch(:include_id, true)
-
55
include_id = options.fetch(:include_id, parent_include_id)
-
55
options[:hidden_field_id] = object.persisted? && include_id
-
55
@template.fields_for(name, object, options, &block)
-
end
-
-
1
def nested_child_index(name)
-
41
@nested_child_index[name] ||= -1
-
41
@nested_child_index[name] += 1
-
end
-
end
-
end
-
-
1
ActiveSupport.on_load(:action_view) do
-
2
cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder }
-
end
-
end
-
1
require 'cgi'
-
1
require 'erb'
-
1
require 'action_view/helpers/form_helper'
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView
-
# = Action View Form Option Helpers
-
1
module Helpers
-
# Provides a number of methods for turning different kinds of containers into a set of option tags.
-
# == Options
-
# The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash:
-
#
-
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
-
#
-
# For example,
-
#
-
# select("post", "category", Post::CATEGORIES, {include_blank: true})
-
#
-
# could become:
-
#
-
# <select name="post[category]">
-
# <option></option>
-
# <option>joke</option>
-
# <option>poem</option>
-
# </select>
-
#
-
# Another common case is a select tag for an <tt>belongs_to</tt>-associated object.
-
#
-
# Example with @post.person_id => 2:
-
#
-
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
-
#
-
# could become:
-
#
-
# <select name="post[person_id]">
-
# <option value="">None</option>
-
# <option value="1">David</option>
-
# <option value="2" selected="selected">Sam</option>
-
# <option value="3">Tobias</option>
-
# </select>
-
#
-
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
-
#
-
# Example:
-
#
-
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
-
#
-
# could become:
-
#
-
# <select name="post[person_id]">
-
# <option value="">Select Person</option>
-
# <option value="1">David</option>
-
# <option value="2">Sam</option>
-
# <option value="3">Tobias</option>
-
# </select>
-
#
-
# Like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
-
# option to be in the +html_options+ parameter.
-
#
-
# Example:
-
#
-
# select("album[]", "genre", %w[rap rock country], {}, { index: nil })
-
#
-
# becomes:
-
#
-
# <select name="album[][genre]" id="album__genre">
-
# <option value="rap">rap</option>
-
# <option value="rock">rock</option>
-
# <option value="country">country</option>
-
# </select>
-
#
-
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
-
#
-
# Example:
-
#
-
# select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
-
#
-
# could become:
-
#
-
# <select name="post[category]">
-
# <option></option>
-
# <option>joke</option>
-
# <option>poem</option>
-
# <option disabled="disabled">restricted</option>
-
# </select>
-
#
-
# When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
-
#
-
# Example:
-
#
-
# collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
-
#
-
# If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
-
# <select name="post[category_id]">
-
# <option value="1" disabled="disabled">2008 stuff</option>
-
# <option value="2" disabled="disabled">Christmas</option>
-
# <option value="3">Jokes</option>
-
# <option value="4">Poems</option>
-
# </select>
-
#
-
1
module FormOptionsHelper
-
# ERB::Util can mask some helpers like textilize. Make sure to include them.
-
1
include TextHelper
-
-
# Create a select tag and a series of contained option tags for the provided object and method.
-
# The option currently held by the object will be selected, provided that the object is available.
-
#
-
# There are two possible formats for the choices parameter, corresponding to other helpers' output:
-
# * A flat collection: see options_for_select
-
# * A nested collection: see grouped_options_for_select
-
#
-
# Example with @post.person_id => 1:
-
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true })
-
#
-
# could become:
-
#
-
# <select name="post[person_id]">
-
# <option value=""></option>
-
# <option value="1" selected="selected">David</option>
-
# <option value="2">Sam</option>
-
# <option value="3">Tobias</option>
-
# </select>
-
#
-
# This can be used to provide a default set of options in the standard way: before rendering the create form, a
-
# new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
-
# to the database. Instead, a second model object is created when the create request is received.
-
# This allows the user to submit a form page more than once with the expected results of creating multiple records.
-
# In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
-
#
-
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>selected: value</tt> to use a different selection
-
# or <tt>selected: nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
-
# tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
-
#
-
# ==== Gotcha
-
#
-
# The HTML specification says when +multiple+ parameter passed to select and all options got deselected
-
# web browsers do not send any value to server. Unfortunately this introduces a gotcha:
-
# if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
-
# the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
-
# any mass-assignment idiom like
-
#
-
# @user.update_attributes(params[:user])
-
#
-
# wouldn't update roles.
-
#
-
# To prevent this the helper generates an auxiliary hidden field before
-
# every multiple select. The hidden field has the same name as multiple select and blank value.
-
#
-
# This way, the client either sends only the hidden field (representing
-
# the deselected multiple select box), or both fields. Since the HTML specification
-
# says key/value pairs have to be sent in the same order they appear in the
-
# form, and parameters extraction gets the last occurrence of any repeated
-
# key in the query string, that works for ordinary forms.
-
#
-
# In case if you don't want the helper to generate this hidden field you can specify <tt>include_hidden: false</tt> option.
-
#
-
1
def select(object, method, choices, options = {}, html_options = {})
-
44
Tags::Select.new(object, method, self, choices, options, html_options).render
-
end
-
-
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
-
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
-
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
-
# or <tt>:include_blank</tt> in the +options+ hash.
-
#
-
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
-
# of +collection+. The return values are used as the +value+ attribute and contents of each
-
# <tt><option></tt> tag, respectively. They can also be any object that responds to +call+, such
-
# as a +proc+, that will be called for each member of the +collection+ to
-
# retrieve the value/text.
-
#
-
# Example object structure for use with this method:
-
# class Post < ActiveRecord::Base
-
# belongs_to :author
-
# end
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# def name_with_initial
-
# "#{first_name.first}. #{last_name}"
-
# end
-
# end
-
#
-
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
-
# collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true)
-
#
-
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
-
# <select name="post[author_id]">
-
# <option value="">Please select</option>
-
# <option value="1" selected="selected">D. Heinemeier Hansson</option>
-
# <option value="2">D. Thomas</option>
-
# <option value="3">M. Clark</option>
-
# </select>
-
1
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
-
12
Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render
-
end
-
-
# Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
-
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
-
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
-
# or <tt>:include_blank</tt> in the +options+ hash.
-
#
-
# Parameters:
-
# * +object+ - The instance of the class to be used for the select tag
-
# * +method+ - The attribute of +object+ corresponding to the select tag
-
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
-
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
-
# array of child objects representing the <tt><option></tt> tags.
-
# * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
-
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
-
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
-
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
-
# * +option_value_method+ - The name of a method which, when called on a child object of a member of
-
# +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
-
#
-
# Example object structure for use with this method:
-
# class Continent < ActiveRecord::Base
-
# has_many :countries
-
# # attribs: id, name
-
# end
-
# class Country < ActiveRecord::Base
-
# belongs_to :continent
-
# # attribs: id, name, continent_id
-
# end
-
# class City < ActiveRecord::Base
-
# belongs_to :country
-
# # attribs: id, name, country_id
-
# end
-
#
-
# Sample usage:
-
# grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
-
#
-
# Possible output:
-
# <select name="city[country_id]">
-
# <optgroup label="Africa">
-
# <option value="1">South Africa</option>
-
# <option value="3">Somalia</option>
-
# </optgroup>
-
# <optgroup label="Europe">
-
# <option value="7" selected="selected">Denmark</option>
-
# <option value="2">Ireland</option>
-
# </optgroup>
-
# </select>
-
#
-
1
def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
-
4
Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
-
end
-
-
# Return select and option tags for the given object and method, using
-
# #time_zone_options_for_select to generate the list of option tags.
-
#
-
# In addition to the <tt>:include_blank</tt> option documented above,
-
# this method also supports a <tt>:model</tt> option, which defaults
-
# to ActiveSupport::TimeZone. This may be used by users to specify a
-
# different time zone model object. (See +time_zone_options_for_select+
-
# for more information.)
-
#
-
# You can also supply an array of ActiveSupport::TimeZone objects
-
# as +priority_zones+, so that they will be listed above the rest of the
-
# (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience
-
# for obtaining a list of the US time zones, or a Regexp to select the zones
-
# of your choice)
-
#
-
# Finally, this method supports a <tt>:default</tt> option, which selects
-
# a default ActiveSupport::TimeZone if the object's time zone is +nil+.
-
#
-
# time_zone_select( "user", "time_zone", nil, include_blank: true)
-
#
-
# time_zone_select( "user", "time_zone", nil, default: "Pacific Time (US & Canada)" )
-
#
-
# time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, default: "Pacific Time (US & Canada)")
-
#
-
# time_zone_select( "user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
-
#
-
# time_zone_select( "user", 'time_zone', /Australia/)
-
#
-
# time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, model: ActiveSupport::TimeZone)
-
1
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
-
16
Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
-
end
-
-
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
-
# where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
-
# the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
-
# become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
-
# may also be an array of values to be selected when using a multiple select.
-
#
-
# Examples (call, result):
-
# options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
-
# # <option value="$">Dollar</option>
-
# # <option value="DKK">Kroner</option>
-
#
-
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
-
# # <option>VISA</option>
-
# # <option selected="selected">MasterCard</option>
-
#
-
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
-
# # <option value="$20">Basic</option>
-
# # <option value="$40" selected="selected">Plus</option>
-
#
-
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
-
# # <option selected="selected">VISA</option>
-
# # <option>MasterCard</option>
-
# # <option selected="selected">Discover</option>
-
#
-
# You can optionally provide html attributes as the last element of the array.
-
#
-
# Examples:
-
# options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"])
-
# # <option value="Denmark">Denmark</option>
-
# # <option value="USA" class="bold" selected="selected">USA</option>
-
# # <option value="Sweden" selected="selected">Sweden</option>
-
#
-
# options_for_select([["Dollar", "$", {class: "bold"}], ["Kroner", "DKK", {onclick: "alert('HI');"}]])
-
# # <option value="$" class="bold">Dollar</option>
-
# # <option value="DKK" onclick="alert('HI');">Kroner</option>
-
#
-
# If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
-
# or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
-
#
-
# Examples:
-
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: "Super Platinum")
-
# # <option value="Free">Free</option>
-
# # <option value="Basic">Basic</option>
-
# # <option value="Advanced">Advanced</option>
-
# # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
-
#
-
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: ["Advanced", "Super Platinum"])
-
# # <option value="Free">Free</option>
-
# # <option value="Basic">Basic</option>
-
# # <option value="Advanced" disabled="disabled">Advanced</option>
-
# # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
-
#
-
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], selected: "Free", disabled: "Super Platinum")
-
# # <option value="Free" selected="selected">Free</option>
-
# # <option value="Basic">Basic</option>
-
# # <option value="Advanced">Advanced</option>
-
# # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
-
#
-
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
-
1
def options_for_select(container, selected = nil)
-
151
return container if String === container
-
-
144
selected, disabled = extract_selected_and_disabled(selected).map do |r|
-
405
Array(r).map { |item| item.to_s }
-
end
-
-
144
container.map do |element|
-
396
html_attributes = option_html_attributes(element)
-
1188
text, value = option_text_and_value(element).map { |item| item.to_s }
-
-
396
html_attributes[:selected] = 'selected' if option_value_selected?(value, selected)
-
396
html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled)
-
396
html_attributes[:value] = value
-
-
396
content_tag_string(:option, text, html_attributes)
-
end.join("\n").html_safe
-
end
-
-
# Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
-
# the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
-
# Example:
-
# options_from_collection_for_select(@people, 'id', 'name')
-
# This will output the same HTML as if you did this:
-
# <option value="#{person.id}">#{person.name}</option>
-
#
-
# This is more often than not used inside a #select_tag like this example:
-
# select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
-
#
-
# If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
-
# will be selected option tag(s).
-
#
-
# If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous
-
# function are the selected values.
-
#
-
# +selected+ can also be a hash, specifying both <tt>:selected</tt> and/or <tt>:disabled</tt> values as required.
-
#
-
# Be sure to specify the same class as the +value_method+ when specifying selected or disabled options.
-
# Failure to do this will produce undesired results. Example:
-
# options_from_collection_for_select(@people, 'id', 'name', '1')
-
# Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string)
-
# options_from_collection_for_select(@people, 'id', 'name', 1)
-
# should produce the desired results.
-
1
def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
-
41
options = collection.map do |element|
-
105
[value_for_collection(element, text_method), value_for_collection(element, value_method)]
-
end
-
41
selected, disabled = extract_selected_and_disabled(selected)
-
41
select_deselect = {
-
:selected => extract_values_from_collection(collection, value_method, selected),
-
:disabled => extract_values_from_collection(collection, value_method, disabled)
-
}
-
-
41
options_for_select(options, select_deselect)
-
end
-
-
# Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but
-
# groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
-
#
-
# Parameters:
-
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
-
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
-
# array of child objects representing the <tt><option></tt> tags.
-
# * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
-
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
-
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
-
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
-
# * +option_value_method+ - The name of a method which, when called on a child object of a member of
-
# +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
-
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
-
# which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
-
# to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are
-
# to be specified.
-
#
-
# Example object structure for use with this method:
-
# class Continent < ActiveRecord::Base
-
# has_many :countries
-
# # attribs: id, name
-
# end
-
# class Country < ActiveRecord::Base
-
# belongs_to :continent
-
# # attribs: id, name, continent_id
-
# end
-
#
-
# Sample usage:
-
# option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)
-
#
-
# Possible output:
-
# <optgroup label="Africa">
-
# <option value="1">Egypt</option>
-
# <option value="4">Rwanda</option>
-
# ...
-
# </optgroup>
-
# <optgroup label="Asia">
-
# <option value="3" selected="selected">China</option>
-
# <option value="12">India</option>
-
# <option value="5">Japan</option>
-
# ...
-
# </optgroup>
-
#
-
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
-
# wrap the output in an appropriate <tt><select></tt> tag.
-
1
def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
-
6
collection.map do |group|
-
12
option_tags = options_from_collection_for_select(
-
group.send(group_method), option_key_method, option_value_method, selected_key)
-
-
12
content_tag(:optgroup, option_tags, :label => group.send(group_label_method))
-
end.join.html_safe
-
end
-
-
# Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
-
# wraps them with <tt><optgroup></tt> tags.
-
#
-
# Parameters:
-
# * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
-
# <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
-
# nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
-
# Ex. ["North America",[["United States","US"],["Canada","CA"]]]
-
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
-
# which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
-
# as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
-
#
-
# Options:
-
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
-
# prepends an option with a generic prompt - "Please select" - or the given prompt string.
-
# * <tt>:divider</tt> - the divider for the options groups.
-
#
-
# Sample usage (Array):
-
# grouped_options = [
-
# ['North America',
-
# [['United States','US'],'Canada']],
-
# ['Europe',
-
# ['Denmark','Germany','France']]
-
# ]
-
# grouped_options_for_select(grouped_options)
-
#
-
# Sample usage (Hash):
-
# grouped_options = {
-
# 'North America' => [['United States','US'], 'Canada'],
-
# 'Europe' => ['Denmark','Germany','France']
-
# }
-
# grouped_options_for_select(grouped_options)
-
#
-
# Possible output:
-
# <optgroup label="Europe">
-
# <option value="Denmark">Denmark</option>
-
# <option value="Germany">Germany</option>
-
# <option value="France">France</option>
-
# </optgroup>
-
# <optgroup label="North America">
-
# <option value="US">United States</option>
-
# <option value="Canada">Canada</option>
-
# </optgroup>
-
#
-
# Sample usage (divider):
-
# grouped_options = [
-
# [['United States','US'], 'Canada'],
-
# ['Denmark','Germany','France']
-
# ]
-
# grouped_options_for_select(grouped_options, nil, divider: '---------')
-
#
-
# Possible output:
-
# <optgroup label="---------">
-
# <option value="US">United States</option>
-
# <option value="Canada">Canada</option>
-
# </optgroup>
-
# <optgroup label="---------">
-
# <option value="Denmark">Denmark</option>
-
# <option value="Germany">Germany</option>
-
# <option value="France">France</option>
-
# </optgroup>
-
#
-
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
-
# wrap the output in an appropriate <tt><select></tt> tag.
-
1
def grouped_options_for_select(grouped_options, selected_key = nil, options = {})
-
11
if options.is_a?(Hash)
-
9
prompt = options[:prompt]
-
9
divider = options[:divider]
-
else
-
2
prompt = options
-
2
options = {}
-
2
message = "Passing the prompt to grouped_options_for_select as an argument is deprecated. " \
-
"Please use an options hash like `{ prompt: #{prompt.inspect} }`."
-
2
ActiveSupport::Deprecation.warn message
-
end
-
-
11
body = "".html_safe
-
-
11
if prompt
-
5
body.safe_concat content_tag(:option, prompt_text(prompt), :value => "")
-
end
-
-
11
grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
-
-
11
grouped_options.each do |container|
-
16
if divider
-
2
label = divider
-
else
-
14
label, container = container
-
end
-
16
body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)
-
end
-
-
11
body
-
end
-
-
# Returns a string of option tags for pretty much any time zone in the
-
# world. Supply a ActiveSupport::TimeZone name as +selected+ to have it
-
# marked as the selected option tag. You can also supply an array of
-
# ActiveSupport::TimeZone objects as +priority_zones+, so that they will
-
# be listed above the rest of the (long) list. (You can use
-
# ActiveSupport::TimeZone.us_zones as a convenience for obtaining a list
-
# of the US time zones, or a Regexp to select the zones of your choice)
-
#
-
# The +selected+ parameter must be either +nil+, or a string that names
-
# a ActiveSupport::TimeZone.
-
#
-
# By default, +model+ is the ActiveSupport::TimeZone constant (which can
-
# be obtained in Active Record as a value object). The only requirement
-
# is that the +model+ parameter be an object that responds to +all+, and
-
# returns an array of objects that represent time zones.
-
#
-
# NOTE: Only the option tags are returned, you have to wrap this call in
-
# a regular HTML select tag.
-
1
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
-
23
zone_options = "".html_safe
-
-
23
zones = model.all
-
166
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
-
-
23
if priority_zones
-
5
if priority_zones.is_a?(Regexp)
-
6
priority_zones = zones.select { |z| z =~ priority_zones }
-
end
-
-
5
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
-
5
zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled')
-
5
zone_options.safe_concat "\n"
-
-
30
zones.reject! { |z| priority_zones.include?(z) }
-
end
-
-
23
zone_options.safe_concat options_for_select(convert_zones[zones], selected)
-
end
-
-
# Returns radio button tags for the collection of existing return values
-
# of +method+ for +object+'s class. The value returned from calling
-
# +method+ on the instance +object+ will be selected. If calling +method+
-
# returns +nil+, no selection is made.
-
#
-
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
-
# methods to be called on each member of +collection+. The return values
-
# are used as the +value+ attribute and contents of each radio button tag,
-
# respectively. They can also be any object that responds to +call+, such
-
# as a +proc+, that will be called for each member of the +collection+ to
-
# retrieve the value/text.
-
#
-
# Example object structure for use with this method:
-
# class Post < ActiveRecord::Base
-
# belongs_to :author
-
# end
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# def name_with_initial
-
# "#{first_name.first}. #{last_name}"
-
# end
-
# end
-
#
-
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
-
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
-
#
-
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
-
# <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
-
# <label for="post_author_id_1">D. Heinemeier Hansson</label>
-
# <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
-
# <label for="post_author_id_2">D. Thomas</label>
-
# <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
-
# <label for="post_author_id_3">M. Clark</label>
-
#
-
# It is also possible to customize the way the elements will be shown by
-
# giving a block to the method:
-
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
-
# b.label { b.radio_button }
-
# end
-
#
-
# The argument passed to the block is a special kind of builder for this
-
# collection, which has the ability to generate the label and radio button
-
# for the current item in the collection, with proper text and value.
-
# Using it, you can change the label and radio button display order or
-
# even use the label as wrapper, as in the example above.
-
#
-
# The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept
-
# extra html options:
-
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
-
# b.label(class: "radio_button") { b.radio_button(class: "radio_button") }
-
# end
-
#
-
# There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
-
# <tt>value</tt>, which are the current item being rendered, its text and value methods,
-
# respectively. You can use them like this:
-
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
-
# b.label(:"data-value" => b.value) { b.radio_button + b.text }
-
# end
-
1
def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
-
16
Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
-
end
-
-
# Returns check box tags for the collection of existing return values of
-
# +method+ for +object+'s class. The value returned from calling +method+
-
# on the instance +object+ will be selected. If calling +method+ returns
-
# +nil+, no selection is made.
-
#
-
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
-
# methods to be called on each member of +collection+. The return values
-
# are used as the +value+ attribute and contents of each check box tag,
-
# respectively. They can also be any object that responds to +call+, such
-
# as a +proc+, that will be called for each member of the +collection+ to
-
# retrieve the value/text.
-
#
-
# Example object structure for use with this method:
-
# class Post < ActiveRecord::Base
-
# has_and_belongs_to_many :author
-
# end
-
# class Author < ActiveRecord::Base
-
# has_and_belongs_to_many :posts
-
# def name_with_initial
-
# "#{first_name.first}. #{last_name}"
-
# end
-
# end
-
#
-
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
-
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
-
#
-
# If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return:
-
# <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
-
# <label for="post_author_ids_1">D. Heinemeier Hansson</label>
-
# <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
-
# <label for="post_author_ids_2">D. Thomas</label>
-
# <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
-
# <label for="post_author_ids_3">M. Clark</label>
-
# <input name="post[author_ids][]" type="hidden" value="" />
-
#
-
# It is also possible to customize the way the elements will be shown by
-
# giving a block to the method:
-
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
-
# b.label { b.check_box }
-
# end
-
#
-
# The argument passed to the block is a special kind of builder for this
-
# collection, which has the ability to generate the label and check box
-
# for the current item in the collection, with proper text and value.
-
# Using it, you can change the label and check box display order or even
-
# use the label as wrapper, as in the example above.
-
#
-
# The builder methods <tt>label</tt> and <tt>check_box</tt> also accept
-
# extra html options:
-
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
-
# b.label(class: "check_box") { b.check_box(class: "check_box") }
-
# end
-
#
-
# There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
-
# <tt>value</tt>, which are the current item being rendered, its text and value methods,
-
# respectively. You can use them like this:
-
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
-
# b.label(:"data-value" => b.value) { b.check_box + b.text }
-
# end
-
1
def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
-
21
Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
-
end
-
-
1
private
-
1
def option_html_attributes(element)
-
402
if Array === element
-
761
element.select { |e| Hash === e }.reduce({}, :merge!)
-
else
-
150
{}
-
end
-
end
-
-
1
def option_text_and_value(option)
-
# Options are [text, value] pairs or strings used for both.
-
396
if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
-
746
option = option.reject { |e| Hash === e } if Array === option
-
253
[option.first, option.last]
-
else
-
143
[option, option]
-
end
-
end
-
-
1
def option_value_selected?(value, selected)
-
792
Array(selected).include? value
-
end
-
-
1
def extract_selected_and_disabled(selected)
-
185
if selected.is_a?(Proc)
-
1
[selected, nil]
-
else
-
184
selected = Array.wrap(selected)
-
184
options = selected.extract_options!.symbolize_keys
-
184
selected_items = options.fetch(:selected, selected)
-
184
[selected_items, options[:disabled]]
-
end
-
end
-
-
1
def extract_values_from_collection(collection, value_method, selected)
-
82
if selected.is_a?(Proc)
-
2
collection.map do |element|
-
6
element.send(value_method) if selected.call(element)
-
end.compact
-
else
-
80
selected
-
end
-
end
-
-
1
def value_for_collection(item, value)
-
376
value.respond_to?(:call) ? value.call(item) : item.send(value)
-
end
-
-
1
def prompt_text(prompt)
-
17
prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
-
end
-
end
-
-
1
class FormBuilder
-
1
def select(method, choices, options = {}, html_options = {})
-
5
@template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
-
end
-
-
1
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
-
3
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
-
end
-
-
1
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
-
1
@template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
-
end
-
-
1
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
-
3
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
-
end
-
-
1
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {})
-
3
@template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
-
end
-
-
1
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {})
-
2
@template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
-
end
-
end
-
end
-
end
-
1
require 'cgi'
-
1
require 'action_view/helpers/tag_helper'
-
1
require 'active_support/core_ext/string/output_safety'
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
-
1
module ActionView
-
# = Action View Form Tag Helpers
-
1
module Helpers
-
# Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like
-
# FormHelper does. Instead, you provide the names and values manually.
-
#
-
# NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
-
# <tt>disabled: true</tt> will give <tt>disabled="disabled"</tt>.
-
1
module FormTagHelper
-
1
extend ActiveSupport::Concern
-
-
1
include UrlHelper
-
1
include TextHelper
-
-
1
mattr_accessor :embed_authenticity_token_in_remote_forms
-
1
self.embed_authenticity_token_in_remote_forms = false
-
-
# Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
-
# ActionController::Base#url_for. The method for the form defaults to POST.
-
#
-
# ==== Options
-
# * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
-
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
-
# If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
-
# is added to simulate the verb over post.
-
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to
-
# pass custom authenticity token string, or to not add authenticity_token field at all
-
# (by passing <tt>false</tt>). Remote forms may omit the embedded authenticity token
-
# by setting <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
-
# This is helpful when you're fragment-caching the form. Remote forms get the
-
# authenticity from the <tt>meta</tt> tag, so embedding is unnecessary unless you
-
# support browsers without JavaScript.
-
# * A list of parameters to feed to the URL the form will be posted to.
-
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
-
# submit behavior. By default this behavior is an ajax submit.
-
#
-
# ==== Examples
-
# form_tag('/posts')
-
# # => <form action="/posts" method="post">
-
#
-
# form_tag('/posts/1', method: :put)
-
# # => <form action="/posts/1" method="post"> ... <input name="_method" type="hidden" value="put" /> ...
-
#
-
# form_tag('/upload', multipart: true)
-
# # => <form action="/upload" method="post" enctype="multipart/form-data">
-
#
-
# <%= form_tag('/posts') do -%>
-
# <div><%= submit_tag 'Save' %></div>
-
# <% end -%>
-
# # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
-
#
-
# <%= form_tag('/posts', remote: true) %>
-
# # => <form action="/posts" method="post" data-remote="true">
-
#
-
# form_tag('http://far.away.com/form', authenticity_token: false)
-
# # form without authenticity token
-
#
-
# form_tag('http://far.away.com/form', authenticity_token: "cf50faa3fe97702ca1ae")
-
# # form with custom authenticity token
-
#
-
1
def form_tag(url_for_options = {}, options = {}, &block)
-
150
html_options = html_options_for_form(url_for_options, options)
-
150
if block_given?
-
143
form_tag_in_block(html_options, &block)
-
else
-
7
form_tag_html(html_options)
-
end
-
end
-
-
# Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple
-
# choice selection box.
-
#
-
# Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or
-
# associated records. <tt>option_tags</tt> is a string containing the option tags for the select box.
-
#
-
# ==== Options
-
# * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:include_blank</tt> - If set to true, an empty option will be create
-
# * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something
-
# * Any other key creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# select_tag "people", options_from_collection_for_select(@people, "id", "name")
-
# # <select id="people" name="people"><option value="1">David</option></select>
-
#
-
# select_tag "people", "<option>David</option>".html_safe
-
# # => <select id="people" name="people"><option>David</option></select>
-
#
-
# select_tag "count", "<option>1</option><option>2</option><option>3</option><option>4</option>".html_safe
-
# # => <select id="count" name="count"><option>1</option><option>2</option>
-
# # <option>3</option><option>4</option></select>
-
#
-
# select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>".html_safe, multiple: true
-
# # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
-
# # <option>Green</option><option>Blue</option></select>
-
#
-
# select_tag "locations", "<option>Home</option><option selected='selected'>Work</option><option>Out</option>".html_safe
-
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
-
# # <option>Out</option></select>
-
#
-
# select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input'
-
# # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option>
-
# # <option>Write</option></select>
-
#
-
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: true
-
# # => <select id="people" name="people"><option value=""></option><option value="1">David</option></select>
-
#
-
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), prompt: "Select something"
-
# # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select>
-
#
-
# select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>".html_safe, disabled: true
-
# # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
-
# # <option>Paris</option><option>Rome</option></select>
-
#
-
# select_tag "credit_card", options_for_select([ "VISA", "MasterCard" ], "MasterCard")
-
# # => <select id="credit_card" name="credit_card"><option>VISA</option>
-
# # <option selected="selected">MasterCard</option></select>
-
1
def select_tag(name, option_tags = nil, options = {})
-
13
option_tags ||= ""
-
13
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
-
-
13
if options.delete(:include_blank)
-
3
option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags)
-
end
-
-
13
if prompt = options.delete(:prompt)
-
4
option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags)
-
end
-
-
13
content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
-
end
-
-
# Creates a standard text field; use these text fields to input smaller chunks of text like a username
-
# or a search query.
-
#
-
# ==== Options
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
-
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
-
# * <tt>:placeholder</tt> - The text contained in the field by default which is removed when the field receives focus.
-
# * Any other key creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# text_field_tag 'name'
-
# # => <input id="name" name="name" type="text" />
-
#
-
# text_field_tag 'query', 'Enter your search query here'
-
# # => <input id="query" name="query" type="text" value="Enter your search query here" />
-
#
-
# text_field_tag 'search', nil, placeholder: 'Enter search term...'
-
# # => <input id="search" name="search" placeholder="Enter search term..." type="text" />
-
#
-
# text_field_tag 'request', nil, class: 'special_input'
-
# # => <input class="special_input" id="request" name="request" type="text" />
-
#
-
# text_field_tag 'address', '', size: 75
-
# # => <input id="address" name="address" size="75" type="text" value="" />
-
#
-
# text_field_tag 'zip', nil, maxlength: 5
-
# # => <input id="zip" maxlength="5" name="zip" type="text" />
-
#
-
# text_field_tag 'payment_amount', '$0.00', disabled: true
-
# # => <input disabled="disabled" id="payment_amount" name="payment_amount" type="text" value="$0.00" />
-
#
-
# text_field_tag 'ip', '0.0.0.0', maxlength: 15, size: 20, class: "ip-input"
-
# # => <input class="ip-input" id="ip" maxlength="15" name="ip" size="20" type="text" value="0.0.0.0" />
-
1
def text_field_tag(name, value = nil, options = {})
-
49
tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
-
end
-
-
# Creates a label element. Accepts a block.
-
#
-
# ==== Options
-
# * Creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# label_tag 'name'
-
# # => <label for="name">Name</label>
-
#
-
# label_tag 'name', 'Your name'
-
# # => <label for="name">Your Name</label>
-
#
-
# label_tag 'name', nil, class: 'small_label'
-
# # => <label for="name" class="small_label">Name</label>
-
1
def label_tag(name = nil, content_or_options = nil, options = nil, &block)
-
134
if block_given? && content_or_options.is_a?(Hash)
-
1
options = content_or_options = content_or_options.stringify_keys
-
else
-
133
options ||= {}
-
133
options = options.stringify_keys
-
end
-
134
options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
-
134
content_tag :label, content_or_options || name.to_s.humanize, options, &block
-
end
-
-
# Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or
-
# data that should be hidden from the user.
-
#
-
# ==== Options
-
# * Creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# hidden_field_tag 'tags_list'
-
# # => <input id="tags_list" name="tags_list" type="hidden" />
-
#
-
# hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@'
-
# # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
-
#
-
# hidden_field_tag 'collected_input', '', onchange: "alert('Input collected!')"
-
# # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
-
# # type="hidden" value="" />
-
1
def hidden_field_tag(name, value = nil, options = {})
-
23
text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
-
end
-
-
# Creates a file upload field. If you are using file uploads then you will also need
-
# to set the multipart option for the form tag:
-
#
-
# <%= form_tag '/upload', multipart: true do %>
-
# <label for="file">File to Upload</label> <%= file_field_tag "file" %>
-
# <%= submit_tag %>
-
# <% end %>
-
#
-
# The specified URL will then be passed a File object containing the selected file, or if the field
-
# was left blank, a StringIO object.
-
#
-
# ==== Options
-
# * Creates standard HTML attributes for the tag.
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
#
-
# ==== Examples
-
# file_field_tag 'attachment'
-
# # => <input id="attachment" name="attachment" type="file" />
-
#
-
# file_field_tag 'avatar', class: 'profile_input'
-
# # => <input class="profile_input" id="avatar" name="avatar" type="file" />
-
#
-
# file_field_tag 'picture', disabled: true
-
# # => <input disabled="disabled" id="picture" name="picture" type="file" />
-
#
-
# file_field_tag 'resume', value: '~/resume.doc'
-
# # => <input id="resume" name="resume" type="file" value="~/resume.doc" />
-
#
-
# file_field_tag 'user_pic', accept: 'image/png,image/gif,image/jpeg'
-
# # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
-
#
-
# file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html'
-
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
-
1
def file_field_tag(name, options = {})
-
2
text_field_tag(name, nil, options.update("type" => "file"))
-
end
-
-
# Creates a password field, a masked text field that will hide the users input behind a mask character.
-
#
-
# ==== Options
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
-
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
-
# * Any other key creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# password_field_tag 'pass'
-
# # => <input id="pass" name="pass" type="password" />
-
#
-
# password_field_tag 'secret', 'Your secret here'
-
# # => <input id="secret" name="secret" type="password" value="Your secret here" />
-
#
-
# password_field_tag 'masked', nil, class: 'masked_input_field'
-
# # => <input class="masked_input_field" id="masked" name="masked" type="password" />
-
#
-
# password_field_tag 'token', '', size: 15
-
# # => <input id="token" name="token" size="15" type="password" value="" />
-
#
-
# password_field_tag 'key', nil, maxlength: 16
-
# # => <input id="key" maxlength="16" name="key" type="password" />
-
#
-
# password_field_tag 'confirm_pass', nil, disabled: true
-
# # => <input disabled="disabled" id="confirm_pass" name="confirm_pass" type="password" />
-
#
-
# password_field_tag 'pin', '1234', maxlength: 4, size: 6, class: "pin_input"
-
# # => <input class="pin_input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" />
-
1
def password_field_tag(name = "password", value = nil, options = {})
-
1
text_field_tag(name, value, options.update("type" => "password"))
-
end
-
-
# Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
-
#
-
# ==== Options
-
# * <tt>:size</tt> - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10").
-
# * <tt>:rows</tt> - Specify the number of rows in the textarea
-
# * <tt>:cols</tt> - Specify the number of columns in the textarea
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:escape</tt> - By default, the contents of the text input are HTML escaped.
-
# If you need unescaped contents, set this to false.
-
# * Any other key creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# text_area_tag 'post'
-
# # => <textarea id="post" name="post"></textarea>
-
#
-
# text_area_tag 'bio', @user.bio
-
# # => <textarea id="bio" name="bio">This is my biography.</textarea>
-
#
-
# text_area_tag 'body', nil, rows: 10, cols: 25
-
# # => <textarea cols="25" id="body" name="body" rows="10"></textarea>
-
#
-
# text_area_tag 'body', nil, size: "25x10"
-
# # => <textarea name="body" id="body" cols="25" rows="10"></textarea>
-
#
-
# text_area_tag 'description', "Description goes here.", disabled: true
-
# # => <textarea disabled="disabled" id="description" name="description">Description goes here.</textarea>
-
#
-
# text_area_tag 'comment', nil, class: 'comment_input'
-
# # => <textarea class="comment_input" id="comment" name="comment"></textarea>
-
1
def text_area_tag(name, content = nil, options = {})
-
8
options = options.stringify_keys
-
-
8
if size = options.delete("size")
-
5
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
-
end
-
-
14
escape = options.delete("escape") { true }
-
8
content = ERB::Util.html_escape(content) if escape
-
-
8
content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
-
end
-
-
# Creates a check box form input tag.
-
#
-
# ==== Options
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Examples
-
# check_box_tag 'accept'
-
# # => <input id="accept" name="accept" type="checkbox" value="1" />
-
#
-
# check_box_tag 'rock', 'rock music'
-
# # => <input id="rock" name="rock" type="checkbox" value="rock music" />
-
#
-
# check_box_tag 'receive_email', 'yes', true
-
# # => <input checked="checked" id="receive_email" name="receive_email" type="checkbox" value="yes" />
-
#
-
# check_box_tag 'tos', 'yes', false, class: 'accept_tos'
-
# # => <input class="accept_tos" id="tos" name="tos" type="checkbox" value="yes" />
-
#
-
# check_box_tag 'eula', 'accepted', false, disabled: true
-
# # => <input disabled="disabled" id="eula" name="eula" type="checkbox" value="accepted" />
-
1
def check_box_tag(name, value = "1", checked = false, options = {})
-
4
html_options = { "type" => "checkbox", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
-
4
html_options["checked"] = "checked" if checked
-
4
tag :input, html_options
-
end
-
-
# Creates a radio button; use groups of radio buttons named the same to allow users to
-
# select from a group of options.
-
#
-
# ==== Options
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Examples
-
# radio_button_tag 'gender', 'male'
-
# # => <input id="gender_male" name="gender" type="radio" value="male" />
-
#
-
# radio_button_tag 'receive_updates', 'no', true
-
# # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
-
#
-
# radio_button_tag 'time_slot', "3:00 p.m.", false, disabled: true
-
# # => <input disabled="disabled" id="time_slot_300_pm" name="time_slot" type="radio" value="3:00 p.m." />
-
#
-
# radio_button_tag 'color', "green", true, class: "color_input"
-
# # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" />
-
1
def radio_button_tag(name, value, checked = false, options = {})
-
8
html_options = { "type" => "radio", "name" => name, "id" => "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}", "value" => value }.update(options.stringify_keys)
-
8
html_options["checked"] = "checked" if checked
-
8
tag :input, html_options
-
end
-
-
# Creates a submit button with the text <tt>value</tt> as the caption.
-
#
-
# ==== Options
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>:disabled</tt> - If true, the user will not be able to use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Data attributes
-
#
-
# * <tt>confirm: 'question?'</tt> - If present the unobtrusive JavaScript
-
# drivers will provide a prompt with the question specified. If the user accepts,
-
# the form is processed normally, otherwise no action is taken.
-
# * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
-
# disabled version of the submit button when the form is submitted. This feature is
-
# provided by the unobtrusive JavaScript driver.
-
#
-
# ==== Examples
-
# submit_tag
-
# # => <input name="commit" type="submit" value="Save changes" />
-
#
-
# submit_tag "Edit this article"
-
# # => <input name="commit" type="submit" value="Edit this article" />
-
#
-
# submit_tag "Save edits", disabled: true
-
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
-
#
-
# submit_tag "Complete sale", data: { disable_with: "Please wait..." }
-
# # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" />
-
#
-
# submit_tag nil, class: "form_submit"
-
# # => <input class="form_submit" name="commit" type="submit" />
-
#
-
# submit_tag "Edit", class: "edit_button"
-
# # => <input class="edit_button" name="commit" type="submit" value="Edit" />
-
#
-
# submit_tag "Save", data: { confirm: "Are you sure?" }
-
# # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />
-
#
-
1
def submit_tag(value = "Save changes", options = {})
-
14
options = options.stringify_keys
-
-
14
if disable_with = options.delete("disable_with")
-
message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \
-
"Use 'data: { disable_with: \'Text\' }' instead."
-
ActiveSupport::Deprecation.warn message
-
-
options["data-disable-with"] = disable_with
-
end
-
-
14
if confirm = options.delete("confirm")
-
1
message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
-
"Use 'data: { confirm: \'Text\' }' instead'."
-
1
ActiveSupport::Deprecation.warn message
-
-
1
options["data-confirm"] = confirm
-
end
-
-
14
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
-
end
-
-
# Creates a button element that defines a <tt>submit</tt> button,
-
# <tt>reset</tt>button or a generic button which can be used in
-
# JavaScript, for example. You can use the button tag as a regular
-
# submit tag but it isn't supported in legacy browsers. However,
-
# the button tag allows richer labels such as images and emphasis,
-
# so this helper will also accept a block.
-
#
-
# ==== Options
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>:disabled</tt> - If true, the user will not be able to
-
# use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Data attributes
-
#
-
# * <tt>confirm: 'question?'</tt> - If present, the
-
# unobtrusive JavaScript drivers will provide a prompt with
-
# the question specified. If the user accepts, the form is
-
# processed normally, otherwise no action is taken.
-
# * <tt>:disable_with</tt> - Value of this parameter will be
-
# used as the value for a disabled version of the submit
-
# button when the form is submitted. This feature is provided
-
# by the unobtrusive JavaScript driver.
-
#
-
# ==== Examples
-
# button_tag
-
# # => <button name="button" type="submit">Button</button>
-
#
-
# button_tag(type: 'button') do
-
# content_tag(:strong, 'Ask me!')
-
# end
-
# # => <button name="button" type="button">
-
# # <strong>Ask me!</strong>
-
# # </button>
-
#
-
# button_tag "Checkout", data: { disable_with => "Please wait..." }
-
# # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
-
#
-
1
def button_tag(content_or_options = nil, options = nil, &block)
-
13
options = content_or_options if block_given? && content_or_options.is_a?(Hash)
-
13
options ||= {}
-
13
options = options.stringify_keys
-
-
13
if disable_with = options.delete("disable_with")
-
message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \
-
"Use 'data: { disable_with: \'Text\' }' instead."
-
ActiveSupport::Deprecation.warn message
-
-
options["data-disable-with"] = disable_with
-
end
-
-
13
if confirm = options.delete("confirm")
-
1
message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
-
"Use 'data: { confirm: \'Text\' }' instead'."
-
1
ActiveSupport::Deprecation.warn message
-
-
1
options["data-confirm"] = confirm
-
end
-
-
13
options.reverse_merge! 'name' => 'button', 'type' => 'submit'
-
-
13
content_tag :button, content_or_options || 'Button', options, &block
-
end
-
-
# Displays an image which when clicked will submit the form.
-
#
-
# <tt>source</tt> is passed to AssetTagHelper#path_to_image
-
#
-
# ==== Options
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Data attributes
-
#
-
# * <tt>confirm: 'question?'</tt> - This will add a JavaScript confirm
-
# prompt with the question specified. If the user accepts, the form is
-
# processed normally, otherwise no action is taken.
-
#
-
# ==== Examples
-
# image_submit_tag("login.png")
-
# # => <input src="/images/login.png" type="image" />
-
#
-
# image_submit_tag("purchase.png", disabled: true)
-
# # => <input disabled="disabled" src="/images/purchase.png" type="image" />
-
#
-
# image_submit_tag("search.png", class: 'search_button')
-
# # => <input class="search_button" src="/images/search.png" type="image" />
-
#
-
# image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button")
-
# # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
-
#
-
# image_submit_tag("save.png", data: { confirm: "Are you sure?" })
-
# # => <input src="/images/save.png" data-confirm="Are you sure?" type="image" />
-
1
def image_submit_tag(source, options = {})
-
3
options = options.stringify_keys
-
-
3
if confirm = options.delete("confirm")
-
1
message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
-
"Use 'data: { confirm: \'Text\' }' instead'."
-
1
ActiveSupport::Deprecation.warn message
-
-
1
options["data-confirm"] = confirm
-
end
-
-
3
tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options)
-
end
-
-
# Creates a field set for grouping HTML form elements.
-
#
-
# <tt>legend</tt> will become the fieldset's title (optional as per W3C).
-
# <tt>options</tt> accept the same values as tag.
-
#
-
# ==== Examples
-
# <%= field_set_tag do %>
-
# <p><%= text_field_tag 'name' %></p>
-
# <% end %>
-
# # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
-
#
-
# <%= field_set_tag 'Your details' do %>
-
# <p><%= text_field_tag 'name' %></p>
-
# <% end %>
-
# # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
-
#
-
# <%= field_set_tag nil, class: 'format' do %>
-
# <p><%= text_field_tag 'name' %></p>
-
# <% end %>
-
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
-
1
def field_set_tag(legend = nil, options = nil, &block)
-
7
output = tag(:fieldset, options, true)
-
7
output.safe_concat(content_tag(:legend, legend)) unless legend.blank?
-
7
output.concat(capture(&block)) if block_given?
-
7
output.safe_concat("</fieldset>")
-
end
-
-
# Creates a text field of type "color".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
1
def color_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
-
end
-
-
# Creates a text field of type "search".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
1
def search_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "search"))
-
end
-
-
# Creates a text field of type "tel".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
1
def telephone_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "tel"))
-
end
-
1
alias phone_field_tag telephone_field_tag
-
-
# Creates a text field of type "date".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
1
def date_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
-
end
-
-
# Creates a text field of type "time".
-
#
-
# === Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
1
def time_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
-
end
-
-
# Creates a text field of type "datetime".
-
#
-
# === Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
1
def datetime_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "datetime"))
-
end
-
-
# Creates a text field of type "datetime-local".
-
#
-
# === Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
1
def datetime_local_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local"))
-
end
-
-
# Creates a text field of type "month".
-
#
-
# === Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
1
def month_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "month"))
-
end
-
-
# Creates a text field of type "week".
-
#
-
# === Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
1
def week_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "week"))
-
end
-
-
# Creates a text field of type "url".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
1
def url_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "url"))
-
end
-
-
# Creates a text field of type "email".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
1
def email_field_tag(name, value = nil, options = {})
-
1
text_field_tag(name, value, options.stringify_keys.update("type" => "email"))
-
end
-
-
# Creates a number field.
-
#
-
# ==== Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:in</tt> - A range specifying the <tt>:min</tt> and
-
# <tt>:max</tt> values.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
#
-
# ==== Examples
-
# number_field_tag 'quantity', nil, in: 1...10
-
# # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
-
1
def number_field_tag(name, value = nil, options = {})
-
2
options = options.stringify_keys
-
2
options["type"] ||= "number"
-
2
if range = options.delete("in") || options.delete("within")
-
2
options.update("min" => range.min, "max" => range.max)
-
end
-
2
text_field_tag(name, value, options)
-
end
-
-
# Creates a range form element.
-
#
-
# ==== Options
-
# * Accepts the same options as number_field_tag.
-
1
def range_field_tag(name, value = nil, options = {})
-
1
number_field_tag(name, value, options.stringify_keys.update("type" => "range"))
-
end
-
-
# Creates the hidden UTF8 enforcer tag. Override this method in a helper
-
# to customize the tag.
-
1
def utf8_enforcer_tag
-
150
tag(:input, :type => "hidden", :name => "utf8", :value => "✓".html_safe)
-
end
-
-
1
private
-
1
def html_options_for_form(url_for_options, options)
-
150
options.stringify_keys.tap do |html_options|
-
150
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
-
# The following URL is unescaped, this is just a hash of options, and it is the
-
# responsibility of the caller to escape all the values.
-
150
html_options["action"] = url_for(url_for_options)
-
150
html_options["accept-charset"] = "UTF-8"
-
-
150
html_options["data-remote"] = true if html_options.delete("remote")
-
-
if html_options["data-remote"] &&
-
150
!embed_authenticity_token_in_remote_forms &&
-
html_options["authenticity_token"].blank?
-
# The authenticity token is taken from the meta tag in this case
-
7
html_options["authenticity_token"] = false
-
elsif html_options["authenticity_token"] == true
-
# Include the default authenticity_token, which is only generated when its set to nil,
-
# but we needed the true value to override the default of no authenticity_token on data-remote.
-
4
html_options["authenticity_token"] = nil
-
end
-
end
-
end
-
-
1
def extra_tags_for_form(html_options)
-
150
authenticity_token = html_options.delete("authenticity_token")
-
150
method = html_options.delete("method").to_s
-
-
150
method_tag = case method
-
when /^get$/i # must be case-insensitive, but can't use downcase as might be nil
-
1
html_options["method"] = "get"
-
1
''
-
when /^post$/i, "", nil
-
62
html_options["method"] = "post"
-
62
token_tag(authenticity_token)
-
else
-
87
html_options["method"] = "post"
-
87
method_tag(method) + token_tag(authenticity_token)
-
end
-
-
150
tags = utf8_enforcer_tag << method_tag
-
150
content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
-
end
-
-
1
def form_tag_html(html_options)
-
150
extra_tags = extra_tags_for_form(html_options)
-
150
tag(:form, html_options, true) + extra_tags
-
end
-
-
1
def form_tag_in_block(html_options, &block)
-
143
content = capture(&block)
-
143
output = form_tag_html(html_options)
-
143
output << content
-
143
output.safe_concat("</form>")
-
end
-
-
# see http://www.w3.org/TR/html4/types.html#type-name
-
1
def sanitize_to_id(name)
-
98
name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_")
-
end
-
end
-
end
-
end
-
1
require 'action_view/helpers/tag_helper'
-
-
1
module ActionView
-
1
module Helpers
-
1
module JavaScriptHelper
-
1
JS_ESCAPE_MAP = {
-
'\\' => '\\\\',
-
'</' => '<\/',
-
"\r\n" => '\n',
-
"\n" => '\n',
-
"\r" => '\n',
-
'"' => '\\"',
-
"'" => "\\'"
-
}
-
-
1
JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '
'
-
1
JS_ESCAPE_MAP["\342\200\251".force_encoding('UTF-8').encode!] = '
'
-
-
# Escapes carriage returns and single and double quotes for JavaScript segments.
-
#
-
# Also available through the alias j(). This is particularly helpful in JavaScript responses, like:
-
#
-
# $('some_element').replaceWith('<%=j render 'some/element_template' %>');
-
1
def escape_javascript(javascript)
-
16
if javascript
-
63
result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
-
15
javascript.html_safe? ? result.html_safe : result
-
else
-
1
''
-
end
-
end
-
-
1
alias_method :j, :escape_javascript
-
-
# Returns a JavaScript tag with the +content+ inside. Example:
-
# javascript_tag "alert('All is good')"
-
#
-
# Returns:
-
# <script>
-
# //<![CDATA[
-
# alert('All is good')
-
# //]]>
-
# </script>
-
#
-
# +html_options+ may be a hash of attributes for the <tt>\<script></tt>
-
# tag. Example:
-
# javascript_tag "alert('All is good')", defer: 'defer'
-
# # => <script defer="defer">alert('All is good')</script>
-
#
-
# Instead of passing the content as an argument, you can also use a block
-
# in which case, you pass your +html_options+ as the first parameter.
-
# <%= javascript_tag defer: 'defer' do -%>
-
# alert('All is good')
-
# <% end -%>
-
1
def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
-
4
content =
-
if block_given?
-
2
html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
-
2
capture(&block)
-
else
-
2
content_or_options_with_block
-
end
-
-
4
content_tag(:script, javascript_cdata_section(content), html_options)
-
end
-
-
1
def javascript_cdata_section(content) #:nodoc:
-
5
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
-
end
-
-
# Returns a button whose +onclick+ handler triggers the passed JavaScript.
-
#
-
# The helper receives a name, JavaScript code, and an optional hash of HTML options. The
-
# name is used as button label and the JavaScript code goes into its +onclick+ attribute.
-
# If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+.
-
#
-
# button_to_function "Greeting", "alert('Hello world!')", class: "ok"
-
# # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
-
#
-
1
def button_to_function(name, function=nil, html_options={})
-
3
message = "button_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " +
-
"See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript"
-
3
ActiveSupport::Deprecation.warn message
-
-
3
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
-
-
3
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
-
end
-
-
# Returns a link whose +onclick+ handler triggers the passed JavaScript.
-
#
-
# The helper receives a name, JavaScript code, and an optional hash of HTML options. The
-
# name is used as the link text and the JavaScript code goes into the +onclick+ attribute.
-
# If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all
-
# the JavaScript is set, the helper appends "; return false;".
-
#
-
# The +href+ attribute of the tag is set to "#" unless +html_options+ has one.
-
#
-
# link_to_function "Greeting", "alert('Hello world!')", class: "nav_link"
-
# # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
-
#
-
1
def link_to_function(name, function, html_options={})
-
3
message = "link_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " +
-
"See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript"
-
3
ActiveSupport::Deprecation.warn message
-
-
3
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
-
3
href = html_options[:href] || '#'
-
-
3
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
-
end
-
end
-
end
-
end
-
# encoding: utf-8
-
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/string/output_safety'
-
1
require 'active_support/number_helper'
-
-
1
module ActionView
-
# = Action View Number Helpers
-
1
module Helpers #:nodoc:
-
-
# Provides methods for converting numbers into formatted strings.
-
# Methods are provided for phone numbers, currency, percentage,
-
# precision, positional notation, file size and pretty printing.
-
#
-
# Most methods expect a +number+ argument, and will return it
-
# unchanged if can't be converted into a valid number.
-
1
module NumberHelper
-
-
# Raised when argument +number+ param given to the helpers is invalid and
-
# the option :raise is set to +true+.
-
1
class InvalidNumberError < StandardError
-
1
attr_accessor :number
-
1
def initialize(number)
-
7
@number = number
-
end
-
end
-
-
# Formats a +number+ into a US phone number (e.g., (555)
-
# 123-9876). You can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:area_code</tt> - Adds parentheses around the area code.
-
# * <tt>:delimiter</tt> - Specifies the delimiter to use
-
# (defaults to "-").
-
# * <tt>:extension</tt> - Specifies an extension to add to the
-
# end of the generated number.
-
# * <tt>:country_code</tt> - Sets the country code for the phone
-
# number.
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_to_phone(5551234) # => 555-1234
-
# number_to_phone("5551234") # => 555-1234
-
# number_to_phone(1235551234) # => 123-555-1234
-
# number_to_phone(1235551234, area_code: true) # => (123) 555-1234
-
# number_to_phone(1235551234, delimiter: " ") # => 123 555 1234
-
# number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
-
# number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
-
# number_to_phone("123a456") # => 123a456
-
#
-
# number_to_phone("1234a567", raise: true) # => InvalidNumberError
-
#
-
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".")
-
# # => +1.123.555.1234 x 1343
-
1
def number_to_phone(number, options = {})
-
24
return unless number
-
23
options = options.symbolize_keys
-
-
23
parse_float(number, true) if options.delete(:raise)
-
22
ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
-
end
-
-
# Formats a +number+ into a currency string (e.g., $13.65). You
-
# can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the level of precision (defaults
-
# to 2).
-
# * <tt>:unit</tt> - Sets the denomination of the currency
-
# (defaults to "$").
-
# * <tt>:separator</tt> - Sets the separator between the units
-
# (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to ",").
-
# * <tt>:format</tt> - Sets the format for non-negative numbers
-
# (defaults to "%u%n"). Fields are <tt>%u</tt> for the
-
# currency, and <tt>%n</tt> for the number.
-
# * <tt>:negative_format</tt> - Sets the format for negative
-
# numbers (defaults to prepending an hyphen to the formatted
-
# number given by <tt>:format</tt>). Accepts the same fields
-
# than <tt>:format</tt>, except <tt>%n</tt> is here the
-
# absolute value of the number.
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_to_currency(1234567890.50) # => $1,234,567,890.50
-
# number_to_currency(1234567890.506) # => $1,234,567,890.51
-
# number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
-
# number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
-
# number_to_currency("123a456") # => $123a456
-
#
-
# number_to_currency("123a456", raise: true) # => InvalidNumberError
-
#
-
# number_to_currency(-1234567890.50, negative_format: "(%u%n)")
-
# # => ($1,234,567,890.50)
-
# number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "")
-
# # => £1234567890,50
-
# number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "", format: "%n %u")
-
# # => 1234567890,50 £
-
1
def number_to_currency(number, options = {})
-
22
return unless number
-
21
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
-
21
wrap_with_output_safety_handling(number, options.delete(:raise)) {
-
20
ActiveSupport::NumberHelper.number_to_currency(number, options)
-
}
-
end
-
-
# Formats a +number+ as a percentage string (e.g., 65%). You can
-
# customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the #
-
# of significant_digits. If +false+, the # of fractional
-
# digits (defaults to +false+).
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
# * <tt>:format</tt> - Specifies the format of the percentage
-
# string The number field is <tt>%n</tt> (defaults to "%n%").
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_to_percentage(100) # => 100.000%
-
# number_to_percentage("98") # => 98.000%
-
# number_to_percentage(100, precision: 0) # => 100%
-
# number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
-
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
-
# number_to_percentage(1000, locale: :fr) # => 1 000,000%
-
# number_to_percentage("98a") # => 98a%
-
# number_to_percentage(100, format: "%n %") # => 100 %
-
#
-
# number_to_percentage("98a", raise: true) # => InvalidNumberError
-
1
def number_to_percentage(number, options = {})
-
18
return unless number
-
17
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
-
17
wrap_with_output_safety_handling(number, options.delete(:raise)) {
-
16
ActiveSupport::NumberHelper.number_to_percentage(number, options)
-
}
-
end
-
-
# Formats a +number+ with grouped thousands using +delimiter+
-
# (e.g., 12,324). You can customize the format in the +options+
-
# hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to ",").
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_with_delimiter(12345678) # => 12,345,678
-
# number_with_delimiter("123456") # => 123,456
-
# number_with_delimiter(12345678.05) # => 12,345,678.05
-
# number_with_delimiter(12345678, delimiter: ".") # => 12.345.678
-
# number_with_delimiter(12345678, delimiter: ",") # => 12,345,678
-
# number_with_delimiter(12345678.05, separator: " ") # => 12,345,678 05
-
# number_with_delimiter(12345678.05, locale: :fr) # => 12 345 678,05
-
# number_with_delimiter("112a") # => 112a
-
# number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
-
# # => 98 765 432,98
-
#
-
# number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
-
1
def number_with_delimiter(number, options = {})
-
24
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
-
24
wrap_with_output_safety_handling(number, options.delete(:raise)) {
-
23
ActiveSupport::NumberHelper.number_to_delimited(number, options)
-
}
-
end
-
-
# Formats a +number+ with the specified level of
-
# <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
-
# +:significant+ is +false+, and 5 if +:significant+ is +true+).
-
# You can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the #
-
# of significant_digits. If +false+, the # of fractional
-
# digits (defaults to +false+).
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_with_precision(111.2345) # => 111.235
-
# number_with_precision(111.2345, precision: 2) # => 111.23
-
# number_with_precision(13, precision: 5) # => 13.00000
-
# number_with_precision(389.32314, precision: 0) # => 389
-
# number_with_precision(111.2345, significant: true) # => 111
-
# number_with_precision(111.2345, precision: 1, significant: true) # => 100
-
# number_with_precision(13, precision: 5, significant: true) # => 13.000
-
# number_with_precision(111.234, locale: :fr) # => 111,234
-
#
-
# number_with_precision(13, precision: 5, significant: true, strip_insignificant_zeros: true)
-
# # => 13
-
#
-
# number_with_precision(389.32314, precision: 4, significant: true) # => 389.3
-
# number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.')
-
# # => 1.111,23
-
1
def number_with_precision(number, options = {})
-
56
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
-
56
wrap_with_output_safety_handling(number, options.delete(:raise)) {
-
55
ActiveSupport::NumberHelper.number_to_rounded(number, options)
-
}
-
end
-
-
-
# Formats the bytes in +number+ into a more understandable
-
# representation (e.g., giving it 1500 yields 1.5 KB). This
-
# method is useful for reporting file sizes to users. You can
-
# customize the format in the +options+ hash.
-
#
-
# See <tt>number_to_human</tt> if you want to pretty-print a
-
# generic number.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the #
-
# of significant_digits. If +false+, the # of fractional
-
# digits (defaults to +true+)
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +true+)
-
# * <tt>:prefix</tt> - If +:si+ formats the number using the SI
-
# prefix (defaults to :binary)
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_to_human_size(123) # => 123 Bytes
-
# number_to_human_size(1234) # => 1.21 KB
-
# number_to_human_size(12345) # => 12.1 KB
-
# number_to_human_size(1234567) # => 1.18 MB
-
# number_to_human_size(1234567890) # => 1.15 GB
-
# number_to_human_size(1234567890123) # => 1.12 TB
-
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
-
# number_to_human_size(483989, precision: 2) # => 470 KB
-
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
-
#
-
# Non-significant zeros after the fractional separator are
-
# stripped out by default (set
-
# <tt>:strip_insignificant_zeros</tt> to +false+ to change
-
# that):
-
# number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
-
# number_to_human_size(524288000, precision: 5) # => "500 MB"
-
1
def number_to_human_size(number, options = {})
-
56
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
-
56
wrap_with_output_safety_handling(number, options.delete(:raise)) {
-
55
ActiveSupport::NumberHelper.number_to_human_size(number, options)
-
}
-
end
-
-
# Pretty prints (formats and approximates) a number in a way it
-
# is more readable by humans (eg.: 1200000000 becomes "1.2
-
# Billion"). This is useful for numbers that can get very large
-
# (and too hard to read).
-
#
-
# See <tt>number_to_human_size</tt> if you want to print a file
-
# size.
-
#
-
# You can also define you own unit-quantifier names if you want
-
# to use other decimal units (eg.: 1500 becomes "1.5
-
# kilometers", 0.150 becomes "150 milliliters", etc). You may
-
# define a wide range of unit quantifiers, even fractional ones
-
# (centi, deci, mili, etc).
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the #
-
# of significant_digits. If +false+, the # of fractional
-
# digits (defaults to +true+)
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +true+)
-
# * <tt>:units</tt> - A Hash of unit quantifier names. Or a
-
# string containing an i18n scope where to find this hash. It
-
# might have the following keys:
-
# * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
-
# *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
-
# *<tt>:billion</tt>, <tt>:trillion</tt>,
-
# *<tt>:quadrillion</tt>
-
# * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
-
# *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
-
# *<tt>:pico</tt>, <tt>:femto</tt>
-
# * <tt>:format</tt> - Sets the format of the output string
-
# (defaults to "%n %u"). The field types are:
-
# * %u - The quantifier (ex.: 'thousand')
-
# * %n - The number
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_to_human(123) # => "123"
-
# number_to_human(1234) # => "1.23 Thousand"
-
# number_to_human(12345) # => "12.3 Thousand"
-
# number_to_human(1234567) # => "1.23 Million"
-
# number_to_human(1234567890) # => "1.23 Billion"
-
# number_to_human(1234567890123) # => "1.23 Trillion"
-
# number_to_human(1234567890123456) # => "1.23 Quadrillion"
-
# number_to_human(1234567890123456789) # => "1230 Quadrillion"
-
# number_to_human(489939, precision: 2) # => "490 Thousand"
-
# number_to_human(489939, precision: 4) # => "489.9 Thousand"
-
# number_to_human(1234567, precision: 4,
-
# significant: false) # => "1.2346 Million"
-
# number_to_human(1234567, precision: 1,
-
# separator: ',',
-
# significant: false) # => "1,2 Million"
-
#
-
# Non-significant zeros after the decimal separator are stripped
-
# out by default (set <tt>:strip_insignificant_zeros</tt> to
-
# +false+ to change that):
-
# number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
-
# number_to_human(500000000, precision: 5) # => "500 Million"
-
#
-
# ==== Custom Unit Quantifiers
-
#
-
# You can also use your own custom unit quantifiers:
-
# number_to_human(500000, units: {unit: "ml", thousand: "lt"}) # => "500 lt"
-
#
-
# If in your I18n locale you have:
-
# distance:
-
# centi:
-
# one: "centimeter"
-
# other: "centimeters"
-
# unit:
-
# one: "meter"
-
# other: "meters"
-
# thousand:
-
# one: "kilometer"
-
# other: "kilometers"
-
# billion: "gazillion-distance"
-
#
-
# Then you could do:
-
#
-
# number_to_human(543934, units: :distance) # => "544 kilometers"
-
# number_to_human(54393498, units: :distance) # => "54400 kilometers"
-
# number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
-
# number_to_human(343, units: :distance, precision: 1) # => "300 meters"
-
# number_to_human(1, units: :distance) # => "1 meter"
-
# number_to_human(0.34, units: :distance) # => "34 centimeters"
-
#
-
1
def number_to_human(number, options = {})
-
50
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
-
50
wrap_with_output_safety_handling(number, options.delete(:raise)) {
-
49
ActiveSupport::NumberHelper.number_to_human(number, options)
-
}
-
end
-
-
1
private
-
-
1
def escape_unsafe_delimiters_and_separators(options)
-
224
options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
-
224
options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
-
224
options
-
end
-
-
1
def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
-
224
valid_float = valid_float?(number)
-
224
raise InvalidNumberError, number if raise_on_invalid && !valid_float
-
-
218
formatted_number = yield
-
-
218
if valid_float || number.html_safe?
-
200
formatted_number.html_safe
-
else
-
18
formatted_number
-
end
-
end
-
-
1
def valid_float?(number)
-
224
!parse_float(number, false).nil?
-
end
-
-
1
def parse_float(number, raise_error)
-
226
Float(number)
-
rescue ArgumentError, TypeError
-
31
raise InvalidNumberError, number if raise_error
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView #:nodoc:
-
# = Action View Raw Output Helper
-
1
module Helpers #:nodoc:
-
1
module OutputSafetyHelper
-
# This method outputs without escaping a string. Since escaping tags is
-
# now default, this can be used when you don't want Rails to automatically
-
# escape tags. This is not recommended if the data is coming from the user's
-
# input.
-
#
-
# For example:
-
#
-
# <%=raw @user.name %>
-
1
def raw(stringish)
-
3
stringish.to_s.html_safe
-
end
-
-
# This method returns a html safe string similar to what <tt>Array#join</tt>
-
# would return. All items in the array, including the supplied separator, are
-
# html escaped unless they are html safe, and the returned string is marked
-
# as html safe.
-
#
-
# safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
-
# # => "<p>foo</p><br /><p>bar</p>"
-
#
-
# safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe)
-
# # => "<p>foo</p><br /><p>bar</p>"
-
#
-
1
def safe_join(array, sep=$,)
-
8
sep = ERB::Util.html_escape(sep)
-
-
24
array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
-
end
-
end
-
end
-
end
-
1
module ActionView
-
# = Action View Record Tag Helpers
-
1
module Helpers
-
1
module RecordTagHelper
-
1
include ActionView::RecordIdentifier
-
-
# Produces a wrapper DIV element with id and class parameters that
-
# relate to the specified Active Record object. Usage example:
-
#
-
# <%= div_for(@person, class: "foo") do %>
-
# <%= @person.name %>
-
# <% end %>
-
#
-
# produces:
-
#
-
# <div id="person_123" class="person foo"> Joe Bloggs </div>
-
#
-
# You can also pass an array of Active Record objects, which will then
-
# get iterated over and yield each record as an argument for the block.
-
# For example:
-
#
-
# <%= div_for(@people, class: "foo") do |person| %>
-
# <%= person.name %>
-
# <% end %>
-
#
-
# produces:
-
#
-
# <div id="person_123" class="person foo"> Joe Bloggs </div>
-
# <div id="person_124" class="person foo"> Jane Bloggs </div>
-
#
-
1
def div_for(record, *args, &block)
-
5
content_tag_for(:div, record, *args, &block)
-
end
-
-
# content_tag_for creates an HTML element with id and class parameters
-
# that relate to the specified Active Record object. For example:
-
#
-
# <%= content_tag_for(:tr, @person) do %>
-
# <td><%= @person.first_name %></td>
-
# <td><%= @person.last_name %></td>
-
# <% end %>
-
#
-
# would produce the following HTML (assuming @person is an instance of
-
# a Person object, with an id value of 123):
-
#
-
# <tr id="person_123" class="person">....</tr>
-
#
-
# If you require the HTML id attribute to have a prefix, you can specify it:
-
#
-
# <%= content_tag_for(:tr, @person, :foo) do %> ...
-
#
-
# produces:
-
#
-
# <tr id="foo_person_123" class="person">...
-
#
-
# You can also pass an array of objects which this method will loop through
-
# and yield the current object to the supplied block, reducing the need for
-
# having to iterate through the object (using <tt>each</tt>) beforehand.
-
# For example (assuming @people is an array of Person objects):
-
#
-
# <%= content_tag_for(:tr, @people) do |person| %>
-
# <td><%= person.first_name %></td>
-
# <td><%= person.last_name %></td>
-
# <% end %>
-
#
-
# produces:
-
#
-
# <tr id="person_123" class="person">...</tr>
-
# <tr id="person_124" class="person">...</tr>
-
#
-
# content_tag_for also accepts a hash of options, which will be converted to
-
# additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
-
# with the default class name for your object. For example:
-
#
-
# <%= content_tag_for(:li, @person, class: "bar") %>...
-
#
-
# produces:
-
#
-
# <li id="person_123" class="person bar">...
-
#
-
1
def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
-
13
options, prefix = prefix, nil if prefix.is_a?(Hash)
-
-
13
Array(single_or_multiple_records).map do |single_record|
-
16
content_tag_for_single_record(tag_name, single_record, prefix, options, &block)
-
end.join("\n").html_safe
-
end
-
-
1
private
-
-
# Called by <tt>content_tag_for</tt> internally to render a content tag
-
# for each record.
-
1
def content_tag_for_single_record(tag_name, record, prefix, options, &block)
-
16
options = options ? options.dup : {}
-
16
options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip
-
16
options[:id] = dom_id(record, prefix)
-
-
16
content_tag(tag_name, capture(record, &block), options)
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
# = Action View Rendering
-
#
-
# Implements methods that allow rendering from a view context.
-
# In order to use this module, all you need is to implement
-
# view_renderer that returns an ActionView::Renderer object.
-
1
module RenderingHelper
-
# Returns the result of a render that's dictated by the options hash. The primary options are:
-
#
-
# * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
-
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
-
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
-
# * <tt>:text</tt> - Renders the text passed in out.
-
#
-
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
-
# as the locals hash.
-
1
def render(options = {}, locals = {}, &block)
-
373
case options
-
when Hash
-
349
if block_given?
-
22
view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block)
-
else
-
327
view_renderer.render(self, options)
-
end
-
else
-
24
view_renderer.render_partial(self, :partial => options, :locals => locals)
-
end
-
end
-
-
# Overwrites _layout_for in the context object so it supports the case a block is
-
# passed to a partial. Returns the contents that are yielded to a layout, given a
-
# name or a block.
-
#
-
# You can think of a layout as a method that is called with a block. If the user calls
-
# <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
-
# If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
-
#
-
# The user can override this default by passing a block to the layout:
-
#
-
# # The template
-
# <%= render layout: "my_layout" do %>
-
# Content
-
# <% end %>
-
#
-
# # The layout
-
# <html>
-
# <%= yield %>
-
# </html>
-
#
-
# In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
-
# this method returns the block that was passed in to <tt>render :layout</tt>, and the response
-
# would be
-
#
-
# <html>
-
# Content
-
# </html>
-
#
-
# Finally, the block can take block arguments, which can be passed in by +yield+:
-
#
-
# # The template
-
# <%= render layout: "my_layout" do |customer| %>
-
# Hello <%= customer.name %>
-
# <% end %>
-
#
-
# # The layout
-
# <html>
-
# <%= yield Struct.new(:name).new("David") %>
-
# </html>
-
#
-
# In this case, the layout would receive the block passed into <tt>render :layout</tt>,
-
# and the struct specified would be passed into the block as an argument. The result
-
# would be
-
#
-
# <html>
-
# Hello David
-
# </html>
-
#
-
1
def _layout_for(*args, &block)
-
179
name = args.first
-
-
179
if block && !name.is_a?(Symbol)
-
22
capture(*args, &block)
-
else
-
157
super
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/try'
-
1
require 'action_view/vendor/html-scanner'
-
-
1
module ActionView
-
# = Action View Sanitize Helpers
-
1
module Helpers #:nodoc:
-
# The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
-
# These helper methods extend Action View making them callable within your template files.
-
1
module SanitizeHelper
-
1
extend ActiveSupport::Concern
-
# This +sanitize+ helper will html encode all tags and strip all attributes that
-
# aren't specifically allowed.
-
#
-
# It also strips href/src tags with invalid protocols, like javascript: especially.
-
# It does its best to counter any tricks that hackers may use, like throwing in
-
# unicode/ascii/hex values to get past the javascript: filters. Check out
-
# the extensive test suite.
-
#
-
# <%= sanitize @article.body %>
-
#
-
# You can add or remove tags/attributes if you want to customize it a bit.
-
# See ActionView::Base for full docs on the available options. You can add
-
# tags/attributes for single uses of +sanitize+ by passing either the
-
# <tt>:attributes</tt> or <tt>:tags</tt> options:
-
#
-
# Normal Use
-
#
-
# <%= sanitize @article.body %>
-
#
-
# Custom Use (only the mentioned tags and attributes are allowed, nothing else)
-
#
-
# <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %>
-
#
-
# Add table tags to the default allowed tags
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
-
# end
-
#
-
# Remove tags to the default allowed tags
-
#
-
# class Application < Rails::Application
-
# config.after_initialize do
-
# ActionView::Base.sanitized_allowed_tags.delete 'div'
-
# end
-
# end
-
#
-
# Change allowed default attributes
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style'
-
# end
-
#
-
# Please note that sanitizing user-provided text does not guarantee that the
-
# resulting markup is valid (conforming to a document type) or even well-formed.
-
# The output may still contain e.g. unescaped '<', '>', '&' characters and
-
# confuse browsers.
-
#
-
1
def sanitize(html, options = {})
-
33
self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
-
end
-
-
# Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
-
1
def sanitize_css(style)
-
1
self.class.white_list_sanitizer.sanitize_css(style)
-
end
-
-
# Strips all HTML tags from the +html+, including comments. This uses the
-
# html-scanner tokenizer and so its HTML parsing ability is limited by
-
# that of html-scanner.
-
#
-
# strip_tags("Strip <i>these</i> tags!")
-
# # => Strip these tags!
-
#
-
# strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
-
# # => Bold no more! See more here...
-
#
-
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
-
# # => Welcome to my website!
-
1
def strip_tags(html)
-
13
self.class.full_sanitizer.sanitize(html)
-
end
-
-
# Strips all link tags from +text+ leaving just the link text.
-
#
-
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
-
# # => Ruby on Rails
-
#
-
# strip_links('Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.')
-
# # => Please e-mail me at me@email.com.
-
#
-
# strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
-
# # => Blog: Visit.
-
1
def strip_links(html)
-
8
self.class.link_sanitizer.sanitize(html)
-
end
-
-
1
module ClassMethods #:nodoc:
-
1
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
-
-
1
def sanitized_protocol_separator
-
white_list_sanitizer.protocol_separator
-
end
-
-
1
def sanitized_uri_attributes
-
white_list_sanitizer.uri_attributes
-
end
-
-
1
def sanitized_bad_tags
-
white_list_sanitizer.bad_tags
-
end
-
-
1
def sanitized_allowed_tags
-
white_list_sanitizer.allowed_tags
-
end
-
-
1
def sanitized_allowed_attributes
-
white_list_sanitizer.allowed_attributes
-
end
-
-
1
def sanitized_allowed_css_properties
-
white_list_sanitizer.allowed_css_properties
-
end
-
-
1
def sanitized_allowed_css_keywords
-
white_list_sanitizer.allowed_css_keywords
-
end
-
-
1
def sanitized_shorthand_css_properties
-
white_list_sanitizer.shorthand_css_properties
-
end
-
-
1
def sanitized_allowed_protocols
-
white_list_sanitizer.allowed_protocols
-
end
-
-
1
def sanitized_protocol_separator=(value)
-
white_list_sanitizer.protocol_separator = value
-
end
-
-
# Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
-
# any object that responds to +sanitize+.
-
#
-
# class Application < Rails::Application
-
# config.action_view.full_sanitizer = MySpecialSanitizer.new
-
# end
-
#
-
1
def full_sanitizer
-
13
@full_sanitizer ||= HTML::FullSanitizer.new
-
end
-
-
# Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
-
# any object that responds to +sanitize+.
-
#
-
# class Application < Rails::Application
-
# config.action_view.link_sanitizer = MySpecialSanitizer.new
-
# end
-
#
-
1
def link_sanitizer
-
8
@link_sanitizer ||= HTML::LinkSanitizer.new
-
end
-
-
# Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
-
# Replace with any object that responds to +sanitize+.
-
#
-
# class Application < Rails::Application
-
# config.action_view.white_list_sanitizer = MySpecialSanitizer.new
-
# end
-
#
-
1
def white_list_sanitizer
-
34
@white_list_sanitizer ||= HTML::WhiteListSanitizer.new
-
end
-
-
# Adds valid HTML attributes that the +sanitize+ helper checks for URIs.
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_uri_attributes = 'lowsrc', 'target'
-
# end
-
#
-
1
def sanitized_uri_attributes=(attributes)
-
HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
-
end
-
-
# Adds to the Set of 'bad' tags for the +sanitize+ helper.
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_bad_tags = 'embed', 'object'
-
# end
-
#
-
1
def sanitized_bad_tags=(attributes)
-
HTML::WhiteListSanitizer.bad_tags.merge(attributes)
-
end
-
-
# Adds to the Set of allowed tags for the +sanitize+ helper.
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
-
# end
-
#
-
1
def sanitized_allowed_tags=(attributes)
-
HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
-
end
-
-
# Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc'
-
# end
-
#
-
1
def sanitized_allowed_attributes=(attributes)
-
HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
-
end
-
-
# Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_allowed_css_properties = 'expression'
-
# end
-
#
-
1
def sanitized_allowed_css_properties=(attributes)
-
HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
-
end
-
-
# Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers.
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_allowed_css_keywords = 'expression'
-
# end
-
#
-
1
def sanitized_allowed_css_keywords=(attributes)
-
HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
-
end
-
-
# Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers.
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_shorthand_css_properties = 'expression'
-
# end
-
#
-
1
def sanitized_shorthand_css_properties=(attributes)
-
HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
-
end
-
-
# Adds to the Set of allowed protocols for the +sanitize+ helper.
-
#
-
# class Application < Rails::Application
-
# config.action_view.sanitized_allowed_protocols = 'ssh', 'feed'
-
# end
-
#
-
1
def sanitized_allowed_protocols=(attributes)
-
HTML::WhiteListSanitizer.allowed_protocols.merge(attributes)
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/output_safety'
-
1
require 'set'
-
-
1
module ActionView
-
# = Action View Tag Helpers
-
1
module Helpers #:nodoc:
-
# Provides methods to generate HTML tags programmatically when you can't use
-
# a Builder. By default, they output XHTML compliant tags.
-
1
module TagHelper
-
1
extend ActiveSupport::Concern
-
1
include CaptureHelper
-
-
1
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
-
autoplay controls loop selected hidden scoped async
-
defer reversed ismap seemless muted required
-
autofocus novalidate formnovalidate open pubdate itemscope).to_set
-
25
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
-
-
1
PRE_CONTENT_STRINGS = {
-
:textarea => "\n"
-
}
-
-
# Returns an empty HTML tag of type +name+ which by default is XHTML
-
# compliant. Set +open+ to true to create an open tag compatible
-
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
-
# hash to +options+. Set +escape+ to false to disable attribute value
-
# escaping.
-
#
-
# ==== Options
-
# You can use symbols or strings for the attribute names.
-
#
-
# Use +true+ with boolean attributes that can render with no value, like
-
# +disabled+ and +readonly+.
-
#
-
# HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
-
# pointing to a hash of sub-attributes.
-
#
-
# To play nicely with JavaScript conventions sub-attributes are dasherized.
-
# For example, a key +user_id+ would render as <tt>data-user-id</tt> and
-
# thus accessed as <tt>dataset.userId</tt>.
-
#
-
# Values are encoded to JSON, with the exception of strings and symbols.
-
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
-
# from 1.4.3.
-
#
-
# ==== Examples
-
# tag("br")
-
# # => <br />
-
#
-
# tag("br", nil, true)
-
# # => <br>
-
#
-
# tag("input", type: 'text', disabled: true)
-
# # => <input type="text" disabled="disabled" />
-
#
-
# tag("img", src: "open & shut.png")
-
# # => <img src="open & shut.png" />
-
#
-
# tag("img", {src: "open & shut.png"}, false, false)
-
# # => <img src="open & shut.png" />
-
#
-
# tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
-
# # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" />
-
1
def tag(name, options = nil, open = false, escape = true)
-
1229
"<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
-
end
-
-
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
-
# HTML attributes by passing an attributes hash to +options+.
-
# Instead of passing the content as an argument, you can also use a block
-
# in which case, you pass your +options+ as the second parameter.
-
# Set escape to false to disable attribute value escaping.
-
#
-
# ==== Options
-
# The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
-
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
-
# symbols or strings for the attribute names.
-
#
-
# ==== Examples
-
# content_tag(:p, "Hello world!")
-
# # => <p>Hello world!</p>
-
# content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
-
# # => <div class="strong"><p>Hello world!</p></div>
-
# content_tag("select", options, multiple: true)
-
# # => <select multiple="multiple">...options...</select>
-
#
-
# <%= content_tag :div, class: "strong" do -%>
-
# Hello world!
-
# <% end -%>
-
# # => <div class="strong">Hello world!</div>
-
1
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
-
14822
if block_given?
-
24
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
-
24
content_tag_string(name, capture(&block), options, escape)
-
else
-
14798
content_tag_string(name, content_or_options_with_block, options, escape)
-
end
-
end
-
-
# Returns a CDATA section with the given +content+. CDATA sections
-
# are used to escape blocks of text containing characters which would
-
# otherwise be recognized as markup. CDATA sections begin with the string
-
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
-
#
-
# cdata_section("<hello world>")
-
# # => <![CDATA[<hello world>]]>
-
#
-
# cdata_section(File.read("hello_world.txt"))
-
# # => <![CDATA[<hello from a text file]]>
-
#
-
# cdata_section("hello]]>world")
-
# # => <![CDATA[hello]]]]><![CDATA[>world]]>
-
1
def cdata_section(content)
-
8
splitted = content.gsub(']]>', ']]]]><![CDATA[>')
-
8
"<![CDATA[#{splitted}]]>".html_safe
-
end
-
-
# Returns an escaped version of +html+ without affecting existing escaped entities.
-
#
-
# escape_once("1 < 2 & 3")
-
# # => "1 < 2 & 3"
-
#
-
# escape_once("<< Accept & Checkout")
-
# # => "<< Accept & Checkout"
-
1
def escape_once(html)
-
1
ERB::Util.html_escape_once(html)
-
end
-
-
1
private
-
-
1
def content_tag_string(name, content, options, escape = true)
-
15252
tag_options = tag_options(options, escape) if options
-
15252
content = ERB::Util.h(content) if escape
-
15252
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
-
end
-
-
1
def tag_options(options, escape = true)
-
16429
return if options.blank?
-
16406
attrs = []
-
16406
options.each_pair do |key, value|
-
21417
if key.to_s == 'data' && value.is_a?(Hash)
-
20
value.each_pair do |k, v|
-
36
attrs << data_tag_option(k, v, escape)
-
end
-
elsif BOOLEAN_ATTRIBUTES.include?(key)
-
660
attrs << boolean_tag_option(key) if value
-
elsif !value.nil?
-
20381
attrs << tag_option(key, value, escape)
-
end
-
end
-
16406
" #{attrs.sort * ' '}".html_safe unless attrs.empty?
-
end
-
-
1
def data_tag_option(key, value, escape)
-
36
key = "data-#{key.to_s.dasherize}"
-
36
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
-
9
value = value.to_json
-
end
-
36
tag_option(key, value, escape)
-
end
-
-
1
def boolean_tag_option(key)
-
651
%(#{key}="#{key}")
-
end
-
-
1
def tag_option(key, value, escape)
-
20417
value = value.join(" ") if value.is_a?(Array)
-
20417
value = ERB::Util.h(value) if escape
-
20417
%(#{key}="#{value}")
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags #:nodoc:
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Base
-
1
autoload :CheckBox
-
1
autoload :CollectionCheckBoxes
-
1
autoload :CollectionRadioButtons
-
1
autoload :CollectionSelect
-
1
autoload :ColorField
-
1
autoload :DateField
-
1
autoload :DateSelect
-
1
autoload :DatetimeField
-
1
autoload :DatetimeLocalField
-
1
autoload :DatetimeSelect
-
1
autoload :EmailField
-
1
autoload :FileField
-
1
autoload :GroupedCollectionSelect
-
1
autoload :HiddenField
-
1
autoload :Label
-
1
autoload :MonthField
-
1
autoload :NumberField
-
1
autoload :PasswordField
-
1
autoload :RadioButton
-
1
autoload :RangeField
-
1
autoload :SearchField
-
1
autoload :Select
-
1
autoload :TelField
-
1
autoload :TextArea
-
1
autoload :TextField
-
1
autoload :TimeField
-
1
autoload :TimeSelect
-
1
autoload :TimeZoneSelect
-
1
autoload :UrlField
-
1
autoload :WeekField
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class Base #:nodoc:
-
1
include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
-
1
include FormOptionsHelper
-
-
1
attr_reader :object
-
-
1
def initialize(object_name, method_name, template_object, options = {})
-
744
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
-
744
@template_object = template_object
-
-
744
@object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
-
744
@object = retrieve_object(options.delete(:object))
-
744
@options = options
-
744
@auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
-
end
-
-
# This is what child classes implement.
-
1
def render
-
1
raise NotImplementedError, "Subclasses must implement a render method"
-
end
-
-
1
private
-
-
1
def value(object)
-
618
object.send @method_name if object
-
end
-
-
1
def value_before_type_cast(object)
-
225
unless object.nil?
-
214
method_before_type_cast = @method_name + "_before_type_cast"
-
-
214
object.respond_to?(method_before_type_cast) ?
-
object.send(method_before_type_cast) :
-
214
value(object)
-
end
-
end
-
-
1
def retrieve_object(object)
-
744
if object
-
222
object
-
522
elsif @template_object.instance_variable_defined?("@#{@object_name}")
-
297
@template_object.instance_variable_get("@#{@object_name}")
-
end
-
rescue NameError
-
# As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
-
5
nil
-
end
-
-
1
def retrieve_autoindex(pre_match)
-
38
object = self.object || @template_object.instance_variable_get("@#{pre_match}")
-
38
if object && object.respond_to?(:to_param)
-
38
object.to_param
-
else
-
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
-
end
-
end
-
-
1
def add_default_name_and_id_for_value(tag_value, options)
-
228
if tag_value.nil?
-
122
add_default_name_and_id(options)
-
else
-
106
specified_id = options["id"]
-
106
add_default_name_and_id(options)
-
-
106
if specified_id.blank? && options["id"].present?
-
100
options["id"] += "_#{sanitized_value(tag_value)}"
-
end
-
end
-
end
-
-
1
def add_default_name_and_id(options)
-
636
if options.has_key?("index")
-
80
options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"]) }
-
71
options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
-
40
options.delete("index")
-
elsif defined?(@auto_index)
-
52
options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) }
-
47
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
-
else
-
1130
options["name"] ||= options.fetch("name"){ tag_name }
-
1112
options["id"] = options.fetch("id"){ tag_id }
-
end
-
-
636
options["name"] += "[]" if options["multiple"]
-
636
options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
-
end
-
-
1
def tag_name
-
581
"#{@object_name}[#{sanitized_method_name}]"
-
end
-
-
1
def tag_name_with_index(index)
-
66
"#{@object_name}[#{index}][#{sanitized_method_name}]"
-
end
-
-
1
def tag_id
-
542
"#{sanitized_object_name}_#{sanitized_method_name}"
-
end
-
-
1
def tag_id_with_index(index)
-
52
"#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
-
end
-
-
1
def sanitized_object_name
-
594
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
-
end
-
-
1
def sanitized_method_name
-
1324
@sanitized_method_name ||= @method_name.sub(/\?$/,"")
-
end
-
-
1
def sanitized_value(value)
-
183
value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
-
end
-
-
1
def select_content_tag(option_tags, options, html_options)
-
76
html_options = html_options.stringify_keys
-
76
add_default_name_and_id(html_options)
-
76
options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options)
-
76
select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
-
-
76
if html_options["multiple"] && options.fetch(:include_hidden, true)
-
5
tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
-
else
-
71
select
-
end
-
end
-
-
1
def select_not_required?(html_options)
-
63
!html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1
-
end
-
-
1
def add_options(option_tags, options, value = nil)
-
76
if options[:include_blank]
-
22
option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
-
end
-
76
if value.blank? && options[:prompt]
-
12
option_tags = content_tag_string('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags
-
end
-
76
option_tags
-
end
-
end
-
end
-
end
-
end
-
1
require 'action_view/helpers/tags/checkable'
-
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class CheckBox < Base #:nodoc:
-
1
include Checkable
-
-
1
def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
-
119
@checked_value = checked_value
-
119
@unchecked_value = unchecked_value
-
119
super(object_name, method_name, template_object, options)
-
end
-
-
1
def render
-
119
options = @options.stringify_keys
-
119
options["type"] = "checkbox"
-
119
options["value"] = @checked_value
-
119
options["checked"] = "checked" if input_checked?(object, options)
-
-
119
if options["multiple"]
-
54
add_default_name_and_id_for_value(@checked_value, options)
-
54
options.delete("multiple")
-
else
-
65
add_default_name_and_id(options)
-
end
-
-
237
include_hidden = options.delete("include_hidden") { true }
-
119
checkbox = tag("input", options)
-
-
119
if include_hidden
-
118
hidden = hidden_field_for_checkbox(options)
-
118
hidden + checkbox
-
else
-
1
checkbox
-
end
-
end
-
-
1
private
-
-
1
def checked?(value)
-
106
case value
-
when TrueClass, FalseClass
-
5
value == !!@checked_value
-
when NilClass
-
35
false
-
when String
-
3
value == @checked_value
-
else
-
63
if value.respond_to?(:include?)
-
10
value.include?(@checked_value)
-
else
-
53
value.to_i == @checked_value.to_i
-
end
-
end
-
end
-
-
1
def hidden_field_for_checkbox(options)
-
118
@unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
module Checkable
-
1
def input_checked?(object, options)
-
168
if options.has_key?("checked")
-
15
checked = options.delete "checked"
-
15
checked == true || checked == "checked"
-
else
-
153
checked?(value(object))
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'action_view/helpers/tags/collection_helpers'
-
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class CollectionCheckBoxes < Base
-
1
include CollectionHelpers
-
-
1
class CheckBoxBuilder < Builder
-
1
def check_box(extra_html_options={})
-
50
html_options = extra_html_options.merge(@input_html_options)
-
50
@template_object.check_box(@object_name, @method_name, html_options, @value, nil)
-
end
-
end
-
-
1
def render
-
21
rendered_collection = render_collection do |item, value, text, default_html_options|
-
50
default_html_options[:multiple] = true
-
50
builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options)
-
-
50
if block_given?
-
10
yield builder
-
else
-
40
builder.check_box + builder.label
-
end
-
end
-
-
# Append a hidden field to make sure something will be sent back to the
-
# server if all check boxes are unchecked.
-
21
hidden = @template_object.hidden_field_tag("#{tag_name}[]", "", :id => nil)
-
-
21
rendered_collection + hidden
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
module CollectionHelpers
-
1
class Builder
-
1
attr_reader :object, :text, :value
-
-
1
def initialize(template_object, object_name, method_name, object,
-
sanitized_attribute_name, text, value, input_html_options)
-
83
@template_object = template_object
-
83
@object_name = object_name
-
83
@method_name = method_name
-
83
@object = object
-
83
@sanitized_attribute_name = sanitized_attribute_name
-
83
@text = text
-
83
@value = value
-
83
@input_html_options = input_html_options
-
end
-
-
1
def label(label_html_options={}, &block)
-
83
@template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block)
-
end
-
end
-
-
1
def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
-
37
@collection = collection
-
37
@value_method = value_method
-
37
@text_method = text_method
-
37
@html_options = html_options
-
-
37
super(object_name, method_name, template_object, options)
-
end
-
-
1
private
-
-
1
def instantiate_builder(builder_class, item, value, text, html_options)
-
83
builder_class.new(@template_object, @object_name, @method_name, item,
-
sanitize_attribute_name(value), text, value, html_options)
-
end
-
-
# Generate default options for collection helpers, such as :checked and
-
# :disabled.
-
1
def default_html_options_for_collection(item, value) #:nodoc:
-
83
html_options = @html_options.dup
-
-
83
[:checked, :selected, :disabled].each do |option|
-
249
next unless current_value = @options[option]
-
-
28
accept = if current_value.respond_to?(:call)
-
3
current_value.call(item)
-
else
-
25
Array(current_value).map(&:to_s).include?(value.to_s)
-
end
-
-
28
if accept
-
15
html_options[option] = true
-
elsif option == :checked
-
6
html_options[option] = false
-
end
-
end
-
-
83
html_options[:object] = @object
-
83
html_options
-
end
-
-
1
def sanitize_attribute_name(value) #:nodoc:
-
83
"#{sanitized_method_name}_#{sanitized_value(value)}"
-
end
-
-
1
def render_collection #:nodoc:
-
37
@collection.map do |item|
-
83
value = value_for_collection(item, @value_method)
-
83
text = value_for_collection(item, @text_method)
-
83
default_html_options = default_html_options_for_collection(item, value)
-
-
83
yield item, value, text, default_html_options
-
end.join.html_safe
-
end
-
end
-
end
-
end
-
end
-
1
require 'action_view/helpers/tags/collection_helpers'
-
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class CollectionRadioButtons < Base
-
1
include CollectionHelpers
-
-
1
class RadioButtonBuilder < Builder
-
1
def radio_button(extra_html_options={})
-
33
html_options = extra_html_options.merge(@input_html_options)
-
33
@template_object.radio_button(@object_name, @method_name, @value, html_options)
-
end
-
end
-
-
1
def render
-
16
render_collection do |item, value, text, default_html_options|
-
33
builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options)
-
-
33
if block_given?
-
10
yield builder
-
else
-
23
builder.radio_button + builder.label
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class CollectionSelect < Base #:nodoc:
-
1
def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
-
12
@collection = collection
-
12
@value_method = value_method
-
12
@text_method = text_method
-
12
@html_options = html_options
-
-
12
super(object_name, method_name, template_object, options)
-
end
-
-
1
def render
-
12
option_tags_options = {
-
11
:selected => @options.fetch(:selected) { value(@object) },
-
:disabled => @options[:disabled]
-
}
-
-
select_content_tag(
-
12
options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options),
-
@options, @html_options
-
)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class ColorField < TextField #:nodoc:
-
1
def render
-
2
options = @options.stringify_keys
-
4
options["value"] = @options.fetch("value") { validate_color_string(value(object)) }
-
2
@options = options
-
2
super
-
end
-
-
1
private
-
-
1
def validate_color_string(string)
-
2
regex = /#[0-9a-fA-F]{6}/
-
2
if regex.match(string)
-
1
string.downcase
-
else
-
1
"#000000"
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class DateField < DatetimeField #:nodoc:
-
1
private
-
-
1
def format_date(value)
-
15
value.try(:strftime, "%Y-%m-%d")
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class DateSelect < Base #:nodoc:
-
1
def initialize(object_name, method_name, template_object, options, html_options)
-
71
@html_options = html_options
-
-
71
super(object_name, method_name, template_object, options)
-
end
-
-
1
def render
-
71
error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe)
-
end
-
-
1
class << self
-
1
def select_type
-
71
@select_type ||= self.name.split("::").last.sub("Select", "").downcase
-
end
-
end
-
-
1
private
-
-
1
def select_type
-
71
self.class.select_type
-
end
-
-
1
def datetime_selector(options, html_options)
-
71
datetime = value(object) || default_datetime(options)
-
71
@auto_index ||= nil
-
-
71
options = options.dup
-
71
options[:field_name] = @method_name
-
71
options[:include_position] = true
-
71
options[:prefix] ||= @object_name
-
71
options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
-
-
71
DateTimeSelector.new(datetime, options, html_options)
-
end
-
-
1
def default_datetime(options)
-
18
return if options[:include_blank] || options[:prompt]
-
-
10
case options[:default]
-
when nil
-
5
Time.current
-
when Date, Time
-
1
options[:default]
-
else
-
4
default = options[:default].dup
-
-
# Rename :minute and :second to :min and :sec
-
4
default[:min] ||= default[:minute]
-
4
default[:sec] ||= default[:second]
-
-
4
time = Time.current
-
-
4
[:year, :month, :day, :hour, :min, :sec].each do |key|
-
24
default[key] ||= time.send(key)
-
end
-
-
4
Time.utc_time(
-
default[:year], default[:month], default[:day],
-
default[:hour], default[:min], default[:sec]
-
)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class DatetimeField < TextField #:nodoc:
-
1
def render
-
30
options = @options.stringify_keys
-
60
options["value"] = @options.fetch("value") { format_date(value(object)) }
-
30
options["min"] = format_date(options["min"])
-
30
options["max"] = format_date(options["max"])
-
30
@options = options
-
30
super
-
end
-
-
1
private
-
-
1
def format_date(value)
-
15
value.try(:strftime, "%Y-%m-%dT%T.%L%z")
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class DatetimeLocalField < DatetimeField #:nodoc:
-
1
class << self
-
1
def field_type
-
10
@field_type ||= "datetime-local"
-
end
-
end
-
-
1
private
-
-
1
def format_date(value)
-
15
value.try(:strftime, "%Y-%m-%dT%T")
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class DatetimeSelect < DateSelect #:nodoc:
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class EmailField < TextField #:nodoc:
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class FileField < TextField #:nodoc:
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class GroupedCollectionSelect < Base #:nodoc:
-
1
def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
-
4
@collection = collection
-
4
@group_method = group_method
-
4
@group_label_method = group_label_method
-
4
@option_key_method = option_key_method
-
4
@option_value_method = option_value_method
-
4
@html_options = html_options
-
-
4
super(object_name, method_name, template_object, options)
-
end
-
-
1
def render
-
4
option_tags_options = {
-
3
:selected => @options.fetch(:selected) { value(@object) },
-
:disabled => @options[:disabled]
-
}
-
-
select_content_tag(
-
4
option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options
-
)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class HiddenField < TextField #:nodoc:
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class Label < Base #:nodoc:
-
1
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
-
125
options ||= {}
-
-
125
content_is_options = content_or_options.is_a?(Hash)
-
125
if content_is_options
-
8
options.merge! content_or_options
-
8
@content = nil
-
else
-
117
@content = content_or_options
-
end
-
-
125
super(object_name, method_name, template_object, options)
-
end
-
-
1
def render(&block)
-
125
options = @options.stringify_keys
-
125
tag_value = options.delete("value")
-
125
name_and_id = options.dup
-
-
125
if name_and_id["for"]
-
5
name_and_id["id"] = name_and_id["for"]
-
else
-
120
name_and_id.delete("id")
-
end
-
-
125
add_default_name_and_id_for_value(tag_value, name_and_id)
-
125
options.delete("index")
-
125
options.delete("namespace")
-
125
options["for"] = name_and_id["id"] unless options.key?("for")
-
-
125
if block_given?
-
17
content = @template_object.capture(&block)
-
else
-
108
content = if @content.blank?
-
33
@object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
-
33
method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
-
-
33
if object.respond_to?(:to_model)
-
30
key = object.class.model_name.i18n_key
-
30
i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
-
end
-
-
33
i18n_default ||= ""
-
33
I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
-
else
-
75
@content.to_s
-
end
-
-
content ||= if object && object.class.respond_to?(:human_attribute_name)
-
24
object.class.human_attribute_name(@method_name)
-
108
end
-
-
108
content ||= @method_name.humanize
-
end
-
-
125
label_tag(name_and_id["id"], content, options)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class MonthField < DatetimeField #:nodoc:
-
1
private
-
-
1
def format_date(value)
-
15
value.try(:strftime, "%Y-%m")
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class NumberField < TextField #:nodoc:
-
1
def render
-
4
options = @options.stringify_keys
-
-
4
if range = options.delete("in") || options.delete("within")
-
4
options.update("min" => range.min, "max" => range.max)
-
end
-
-
4
@options = options
-
4
super
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class PasswordField < TextField #:nodoc:
-
1
def render
-
3
@options = {:value => nil}.merge!(@options)
-
3
super
-
end
-
end
-
end
-
end
-
end
-
1
require 'action_view/helpers/tags/checkable'
-
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class RadioButton < Base #:nodoc:
-
1
include Checkable
-
-
1
def initialize(object_name, method_name, template_object, tag_value, options)
-
49
@tag_value = tag_value
-
49
super(object_name, method_name, template_object, options)
-
end
-
-
1
def render
-
49
options = @options.stringify_keys
-
49
options["type"] = "radio"
-
49
options["value"] = @tag_value
-
49
options["checked"] = "checked" if input_checked?(object, options)
-
49
add_default_name_and_id_for_value(@tag_value, options)
-
49
tag("input", options)
-
end
-
-
1
private
-
-
1
def checked?(value)
-
47
value.to_s == @tag_value.to_s
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class RangeField < NumberField #:nodoc:
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class SearchField < TextField #:nodoc:
-
1
def render
-
2
options = @options.stringify_keys
-
-
2
if options["autosave"]
-
if options["autosave"] == true
-
options["autosave"] = request.host.split(".").reverse.join(".")
-
end
-
options["results"] ||= 10
-
end
-
-
2
if options["onsearch"]
-
options["incremental"] = true unless options.has_key?("incremental")
-
end
-
-
2
super
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class Select < Base #:nodoc:
-
1
def initialize(object_name, method_name, template_object, choices, options, html_options)
-
44
@choices = choices
-
44
@choices = @choices.to_a if @choices.is_a?(Range)
-
44
@html_options = html_options
-
-
44
super(object_name, method_name, template_object, options)
-
end
-
-
1
def render
-
44
option_tags_options = {
-
42
:selected => @options.fetch(:selected) { value(@object) },
-
:disabled => @options[:disabled]
-
}
-
-
44
option_tags = if grouped_choices?
-
2
grouped_options_for_select(@choices, option_tags_options)
-
else
-
42
options_for_select(@choices, option_tags_options)
-
end
-
-
44
select_content_tag(option_tags, @options, @html_options)
-
end
-
-
1
private
-
-
# Grouped choices look like this:
-
#
-
# [nil, []]
-
# { nil => [] }
-
#
-
1
def grouped_choices?
-
44
!@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class TelField < TextField #:nodoc:
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class TextArea < Base #:nodoc:
-
1
def render
-
46
options = @options.stringify_keys
-
46
add_default_name_and_id(options)
-
-
46
if size = options.delete("size")
-
1
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
-
end
-
-
46
content_tag("textarea", options.delete('value') || value_before_type_cast(object), options)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class TextField < Base #:nodoc:
-
1
def render
-
221
options = @options.stringify_keys
-
221
options["size"] = options["maxlength"] unless options.key?("size")
-
221
options["type"] ||= field_type
-
401
options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
-
221
options["value"] &&= ERB::Util.html_escape(options["value"])
-
221
add_default_name_and_id(options)
-
221
tag("input", options)
-
end
-
-
1
class << self
-
1
def field_type
-
431
@field_type ||= self.name.split("::").last.sub("Field", "").downcase
-
end
-
end
-
-
1
private
-
-
1
def field_type
-
441
self.class.field_type
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class TimeField < DatetimeField #:nodoc:
-
1
private
-
-
1
def format_date(value)
-
15
value.try(:strftime, "%T.%L")
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class TimeSelect < DateSelect #:nodoc:
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class TimeZoneSelect < Base #:nodoc:
-
1
def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
-
16
@priority_zones = priority_zones
-
16
@html_options = html_options
-
-
16
super(object_name, method_name, template_object, options)
-
end
-
-
1
def render
-
select_content_tag(
-
16
time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
-
)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class UrlField < TextField #:nodoc:
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags
-
1
class WeekField < DatetimeField #:nodoc:
-
1
private
-
-
1
def format_date(value)
-
15
value.try(:strftime, "%Y-W%W")
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/filters'
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
module ActionView
-
# = Action View Text Helpers
-
1
module Helpers #:nodoc:
-
# The TextHelper module provides a set of methods for filtering, formatting
-
# and transforming strings, which can reduce the amount of inline Ruby code in
-
# your views. These helper methods extend Action View making them callable
-
# within your template files.
-
#
-
# ==== Sanitization
-
#
-
# Most text helpers by default sanitize the given content, but do not escape it.
-
# This means HTML tags will appear in the page but all malicious code will be removed.
-
# Let's look at some examples using the +simple_format+ method:
-
#
-
# simple_format('<a href="http://example.com/">Example</a>')
-
# # => "<p><a href=\"http://example.com/\">Example</a></p>"
-
#
-
# simple_format('<a href="javascript:alert(\'no!\')">Example</a>')
-
# # => "<p><a>Example</a></p>"
-
#
-
# If you want to escape all content, you should invoke the +h+ method before
-
# calling the text helper.
-
#
-
# simple_format h('<a href="http://example.com/">Example</a>')
-
# # => "<p><a href=\"http://example.com/\">Example</a></p>"
-
1
module TextHelper
-
1
extend ActiveSupport::Concern
-
-
1
include SanitizeHelper
-
1
include TagHelper
-
# The preferred method of outputting text in your views is to use the
-
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
-
# do not operate as expected in an eRuby code block. If you absolutely must
-
# output text within a non-output code block (i.e., <% %>), you can use the concat method.
-
#
-
# <%
-
# concat "hello"
-
# # is the equivalent of <%= "hello" %>
-
#
-
# if logged_in
-
# concat "Logged in!"
-
# else
-
# concat link_to('login', action: :login)
-
# end
-
# # will either display "Logged in!" or a login link
-
# %>
-
1
def concat(string)
-
257
output_buffer << string
-
end
-
-
1
def safe_concat(string)
-
8
output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string)
-
end
-
-
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
-
# (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...")
-
# for a total length not exceeding <tt>:length</tt>.
-
#
-
# Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
-
#
-
# Pass a block if you want to show extra content when the text is truncated.
-
#
-
# The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is
-
# +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation
-
# may produce invalid HTML (such as unbalanced or incomplete tags).
-
#
-
# truncate("Once upon a time in a world far far away")
-
# # => "Once upon a time in a world..."
-
#
-
# truncate("Once upon a time in a world far far away", length: 17)
-
# # => "Once upon a ti..."
-
#
-
# truncate("Once upon a time in a world far far away", length: 17, separator: ' ')
-
# # => "Once upon a..."
-
#
-
# truncate("And they found that many people were sleeping better.", length: 25, omission: '... (continued)')
-
# # => "And they f... (continued)"
-
#
-
# truncate("<p>Once upon a time in a world far far away</p>")
-
# # => "<p>Once upon a time in a wo..."
-
#
-
# truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
-
# # => "Once upon a time in a wo...<a href="#">Continue</a>"
-
1
def truncate(text, options = {}, &block)
-
21
if text
-
21
length = options.fetch(:length, 30)
-
-
21
content = text.truncate(length, options)
-
21
content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content)
-
21
content << capture(&block) if block_given? && text.length > length
-
21
content
-
end
-
end
-
-
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
-
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
-
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
-
# '<mark>\1</mark>')
-
#
-
# highlight('You searched for: rails', 'rails')
-
# # => You searched for: <mark>rails</mark>
-
#
-
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
-
# # => You searched for: ruby, rails, dhh
-
#
-
# highlight('You searched for: rails', ['for', 'rails'], highlighter: '<em>\1</em>')
-
# # => You searched <em>for</em>: <em>rails</em>
-
#
-
# highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
-
# # => You searched for: <a href="search?q=rails">rails</a>
-
1
def highlight(text, phrases, options = {})
-
19
highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
-
-
19
text = sanitize(text) if options.fetch(:sanitize, true)
-
19
if text.blank? || phrases.blank?
-
2
text
-
else
-
35
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
-
17
text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
-
end.html_safe
-
end
-
-
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
-
# The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
-
# defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
-
# then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The
-
# <tt>:separator</tt> enable to choose the delimation. The resulting string will be stripped in any case. If the +phrase+
-
# isn't found, nil is returned.
-
#
-
# excerpt('This is an example', 'an', radius: 5)
-
# # => ...s is an exam...
-
#
-
# excerpt('This is an example', 'is', radius: 5)
-
# # => This is a...
-
#
-
# excerpt('This is an example', 'is')
-
# # => This is an example
-
#
-
# excerpt('This next thing is an example', 'ex', radius: 2)
-
# # => ...next...
-
#
-
# excerpt('This is also an example', 'an', radius: 8, omission: '<chop> ')
-
# # => <chop> is also an example
-
#
-
# excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
-
# # => ...a very beautiful...
-
1
def excerpt(text, phrase, options = {})
-
26
return unless text && phrase
-
-
26
separator = options.fetch(:separator, "")
-
26
phrase = Regexp.escape(phrase)
-
26
regex = /#{phrase}/i
-
-
26
return unless matches = text.match(regex)
-
25
phrase = matches[0]
-
-
25
text.split(separator).each do |value|
-
502
if value.match(regex)
-
14
regex = phrase = value
-
14
break
-
end
-
end
-
-
25
first_part, second_part = text.split(regex, 2)
-
-
25
prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
-
25
postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
-
-
25
prefix + (first_part + separator + phrase + separator + second_part).strip + postfix
-
end
-
-
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
-
# +plural+ is supplied, it will use that when count is > 1, otherwise
-
# it will use the Inflector to determine the plural form.
-
#
-
# pluralize(1, 'person')
-
# # => 1 person
-
#
-
# pluralize(2, 'person')
-
# # => 2 people
-
#
-
# pluralize(3, 'person', 'users')
-
# # => 3 users
-
#
-
# pluralize(0, 'person')
-
# # => 0 people
-
1
def pluralize(count, singular, plural = nil)
-
14
word = if (count == 1 || count =~ /^1(\.0+)?$/)
-
5
singular
-
else
-
9
plural || singular.pluralize
-
end
-
-
14
"#{count || 0} #{word}"
-
end
-
-
# Wraps the +text+ into lines no longer than +line_width+ width. This method
-
# breaks on the first whitespace character that does not exceed +line_width+
-
# (which is 80 by default).
-
#
-
# word_wrap('Once upon a time')
-
# # => Once upon a time
-
#
-
# word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
-
# # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined...
-
#
-
# word_wrap('Once upon a time', line_width: 8)
-
# # => Once\nupon a\ntime
-
#
-
# word_wrap('Once upon a time', line_width: 1)
-
# # => Once\nupon\na\ntime
-
1
def word_wrap(text, options = {})
-
3
line_width = options.fetch(:line_width, 80)
-
-
text.split("\n").collect do |line|
-
5
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
-
3
end * "\n"
-
end
-
-
# Returns +text+ transformed into HTML using simple formatting rules.
-
# Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
-
# paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
-
# considered as a linebreak and a <tt><br /></tt> tag is appended. This
-
# method does not remove the newlines from the +text+.
-
#
-
# You can pass any HTML attributes into <tt>html_options</tt>. These
-
# will be added to all created paragraphs.
-
#
-
# ==== Options
-
# * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
-
# * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>
-
#
-
# ==== Examples
-
# my_text = "Here is some basic text...\n...with a line break."
-
#
-
# simple_format(my_text)
-
# # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
-
#
-
# simple_format(my_text, {}, wrapper_tag: "div")
-
# # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
-
#
-
# more_text = "We want to put a paragraph...\n\n...right there."
-
#
-
# simple_format(more_text)
-
# # => "<p>We want to put a paragraph...</p>\n\n<p>...right there.</p>"
-
#
-
# simple_format("Look ma! A class!", class: 'description')
-
# # => "<p class='description'>Look ma! A class!</p>"
-
#
-
# simple_format("<span>I'm allowed!</span> It's true.", {}, sanitize: false)
-
# # => "<p><span>I'm allowed!</span> It's true.</p>"
-
1
def simple_format(text, html_options = {}, options = {})
-
16
wrapper_tag = options.fetch(:wrapper_tag, :p)
-
-
16
text = sanitize(text) if options.fetch(:sanitize, true)
-
16
paragraphs = split_paragraphs(text)
-
-
16
if paragraphs.empty?
-
2
content_tag(wrapper_tag, nil, html_options)
-
else
-
14
paragraphs.map { |paragraph|
-
18
content_tag(wrapper_tag, paragraph, html_options, options[:sanitize])
-
}.join("\n\n").html_safe
-
end
-
end
-
-
# Creates a Cycle object whose _to_s_ method cycles through elements of an
-
# array every time it is called. This can be used for example, to alternate
-
# classes for table rows. You can use named cycles to allow nesting in loops.
-
# Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
-
# named cycle. The default name for a cycle without a +:name+ key is
-
# <tt>"default"</tt>. You can manually reset a cycle by calling reset_cycle
-
# and passing the name of the cycle. The current cycle string can be obtained
-
# anytime using the current_cycle method.
-
#
-
# # Alternate CSS classes for even and odd numbers...
-
# @items = [1,2,3,4]
-
# <table>
-
# <% @items.each do |item| %>
-
# <tr class="<%= cycle("odd", "even") -%>">
-
# <td>item</td>
-
# </tr>
-
# <% end %>
-
# </table>
-
#
-
#
-
# # Cycle CSS classes for rows, and text colors for values within each row
-
# @items = x = [{first: 'Robert', middle: 'Daniel', last: 'James'},
-
# {first: 'Emily', middle: 'Shannon', maiden: 'Pike', last: 'Hicks'},
-
# {first: 'June', middle: 'Dae', last: 'Jones'}]
-
# <% @items.each do |item| %>
-
# <tr class="<%= cycle("odd", "even", name: "row_class") -%>">
-
# <td>
-
# <% item.values.each do |value| %>
-
# <%# Create a named cycle "colors" %>
-
# <span style="color:<%= cycle("red", "green", "blue", name: "colors") -%>">
-
# <%= value %>
-
# </span>
-
# <% end %>
-
# <% reset_cycle("colors") %>
-
# </td>
-
# </tr>
-
# <% end %>
-
1
def cycle(first_value, *values)
-
44
options = values.extract_options!
-
44
name = options.fetch(:name, 'default')
-
-
44
values.unshift(first_value)
-
-
44
cycle = get_cycle(name)
-
44
unless cycle && cycle.values == values
-
13
cycle = set_cycle(name, Cycle.new(*values))
-
end
-
44
cycle.to_s
-
end
-
-
# Returns the current cycle string after a cycle has been started. Useful
-
# for complex table highlighting or any other design need which requires
-
# the current cycle string in more than one place.
-
#
-
# # Alternate background colors
-
# @items = [1,2,3,4]
-
# <% @items.each do |item| %>
-
# <div style="background-color:<%= cycle("red","white","blue") %>">
-
# <span style="background-color:<%= current_cycle %>"><%= item %></span>
-
# </div>
-
# <% end %>
-
1
def current_cycle(name = "default")
-
12
cycle = get_cycle(name)
-
12
cycle.current_value if cycle
-
end
-
-
# Resets a cycle so that it starts from the first element the next time
-
# it is called. Pass in +name+ to reset a named cycle.
-
#
-
# # Alternate CSS classes for even and odd numbers...
-
# @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
-
# <table>
-
# <% @items.each do |item| %>
-
# <tr class="<%= cycle("even", "odd") -%>">
-
# <% item.each do |value| %>
-
# <span style="color:<%= cycle("#333", "#666", "#999", name: "colors") -%>">
-
# <%= value %>
-
# </span>
-
# <% end %>
-
#
-
# <% reset_cycle("colors") %>
-
# </tr>
-
# <% end %>
-
# </table>
-
1
def reset_cycle(name = "default")
-
3
cycle = get_cycle(name)
-
3
cycle.reset if cycle
-
end
-
-
1
class Cycle #:nodoc:
-
1
attr_reader :values
-
-
1
def initialize(first_value, *values)
-
14
@values = values.unshift(first_value)
-
14
reset
-
end
-
-
1
def reset
-
17
@index = 0
-
end
-
-
1
def current_value
-
10
@values[previous_index].to_s
-
end
-
-
1
def to_s
-
51
value = @values[@index].to_s
-
51
@index = next_index
-
51
return value
-
end
-
-
1
private
-
-
1
def next_index
-
51
step_index(1)
-
end
-
-
1
def previous_index
-
10
step_index(-1)
-
end
-
-
1
def step_index(n)
-
61
(@index + n) % @values.size
-
end
-
end
-
-
1
private
-
# The cycle helpers need to store the cycles in a place that is
-
# guaranteed to be reset every time a page is rendered, so it
-
# uses an instance variable of ActionView::Base.
-
1
def get_cycle(name)
-
59
@_cycles = Hash.new unless defined?(@_cycles)
-
59
return @_cycles[name]
-
end
-
-
1
def set_cycle(name, cycle_object)
-
13
@_cycles = Hash.new unless defined?(@_cycles)
-
13
@_cycles[name] = cycle_object
-
end
-
-
1
def split_paragraphs(text)
-
16
return [] if text.blank?
-
-
14
text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t|
-
18
t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
-
end
-
end
-
-
1
def cut_excerpt_part(part_position, part, separator, options)
-
50
return "", "" unless part
-
-
48
radius = options.fetch(:radius, 100)
-
48
omission = options.fetch(:omission, "...")
-
-
48
part = part.split(separator)
-
48
part.delete("")
-
48
affix = part.size > radius ? omission : ""
-
-
48
part = if part_position == :first
-
24
drop_index = [part.length - radius, 0].max
-
24
part.drop(drop_index)
-
else
-
24
part.first(radius)
-
end
-
-
48
return affix, part.join(separator)
-
end
-
end
-
end
-
end
-
1
require 'action_view/helpers/tag_helper'
-
1
require 'i18n/exceptions'
-
-
1
module I18n
-
1
class ExceptionHandler
-
include Module.new {
-
1
def call(exception, locale, key, options)
-
7
exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super
-
end
-
1
}
-
end
-
end
-
-
1
module ActionView
-
# = Action View Translation Helpers
-
1
module Helpers
-
1
module TranslationHelper
-
# Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
-
#
-
# First, it'll pass the <tt>rescue_format: :html</tt> option to I18n so that any
-
# thrown +MissingTranslation+ messages will be turned into inline spans that
-
#
-
# * have a "translation-missing" class set,
-
# * contain the missing key as a title attribute and
-
# * a titleized version of the last key segment as a text.
-
#
-
# E.g. the value returned for a missing translation key :"blog.post.title" will be
-
# <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
-
# This way your views will display rather reasonable strings but it will still
-
# be easy to spot missing translations.
-
#
-
# Second, it'll scope the key by the current partial if the key starts
-
# with a period. So if you call <tt>translate(".foo")</tt> from the
-
# <tt>people/index.html.erb</tt> template, you'll actually be calling
-
# <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
-
# to translate many keys within the same partials and gives you a simple framework
-
# for scoping them consistently. If you don't prepend the key with a period,
-
# nothing is converted.
-
#
-
# Third, it'll mark the translation as safe HTML if the key has the suffix
-
# "_html" or the last element of the key is the word "html". For example,
-
# calling translate("footer_html") or translate("footer.html") will return
-
# a safe HTML string that won't be escaped by other HTML helper methods. This
-
# naming convention helps to identify translations that include HTML tags so that
-
# you know what kind of output to expect when you call translate in a template.
-
1
def translate(key, options = {})
-
30
options.merge!(:rescue_format => :html) unless options.key?(:rescue_format)
-
30
options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
-
30
if html_safe_translation_key?(key)
-
12
html_safe_options = options.dup
-
12
options.except(*I18n::RESERVED_KEYS).each do |name, value|
-
5
unless name == :count && value.is_a?(Numeric)
-
3
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
-
end
-
end
-
12
translation = I18n.translate(scope_key_by_partial(key), html_safe_options)
-
-
12
translation.respond_to?(:html_safe) ? translation.html_safe : translation
-
else
-
18
I18n.translate(scope_key_by_partial(key), options)
-
end
-
end
-
1
alias :t :translate
-
-
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
-
#
-
# See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
-
# for more information.
-
1
def localize(*args)
-
1
I18n.localize(*args)
-
end
-
1
alias :l :localize
-
-
1
private
-
1
def scope_key_by_partial(key)
-
30
if key.to_s.first == "."
-
5
if @virtual_path
-
5
@virtual_path.gsub(%r{/_?}, ".") + key.to_s
-
else
-
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
-
end
-
else
-
25
key
-
end
-
end
-
-
1
def html_safe_translation_key?(key)
-
30
key.to_s =~ /(\b|_|\.)html$/
-
end
-
-
1
def wrap_translate_defaults(defaults)
-
12
new_defaults = []
-
12
defaults = Array(defaults)
-
12
while key = defaults.shift
-
9
if key.is_a?(Symbol)
-
12
new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) }
-
6
break
-
else
-
3
new_defaults << key
-
end
-
end
-
-
12
new_defaults
-
end
-
end
-
end
-
end
-
1
require 'action_view/helpers/javascript_helper'
-
1
require 'active_support/core_ext/array/access'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView
-
# = Action View URL Helpers
-
1
module Helpers #:nodoc:
-
# Provides a set of methods for making links and getting URLs that
-
# depend on the routing subsystem (see ActionDispatch::Routing).
-
# This allows you to use the same format for links in views
-
# and controllers.
-
1
module UrlHelper
-
# This helper may be included in any class that includes the
-
# URL helpers of a routes (routes.url_helpers). Some methods
-
# provided here will only work in the context of a request
-
# (link_to_unless_current, for instance), which must be provided
-
# as a method called #request on the context.
-
-
1
extend ActiveSupport::Concern
-
-
1
include TagHelper
-
-
1
module ClassMethods
-
1
def _url_for_modules
-
191
ActionView::RoutingUrlFor
-
end
-
end
-
-
# Basic implementation of url_for to allow use helpers without routes existence
-
1
def url_for(options = nil) # :nodoc:
-
case options
-
when String
-
options
-
when :back
-
_back_url
-
else
-
raise ArgumentError, "arguments passed to url_for can't be handled. Please require " +
-
"routes or provide your own implementation"
-
end
-
end
-
-
1
def _back_url # :nodoc:
-
4
referrer = controller.respond_to?(:request) && controller.request.env["HTTP_REFERER"]
-
4
referrer || 'javascript:history.back()'
-
end
-
1
protected :_back_url
-
-
# Creates a link tag of the given +name+ using a URL created by the set of +options+.
-
# See the valid options in the documentation for +url_for+. It's also possible to
-
# pass a String instead of an options hash, which generates a link tag that uses the
-
# value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
-
# of an options hash will generate a link to the referrer (a JavaScript back link
-
# will be used in place of a referrer if none exists). If +nil+ is passed as the name
-
# the value of the link itself will become the name.
-
#
-
# ==== Signatures
-
#
-
# link_to(body, url, html_options = {})
-
# # url is a String; you can use URL helpers like
-
# # posts_path
-
#
-
# link_to(body, url_options = {}, html_options = {})
-
# # url_options, except :method, is passed to url_for
-
#
-
# link_to(options = {}, html_options = {}) do
-
# # name
-
# end
-
#
-
# link_to(url, html_options = {}) do
-
# # name
-
# end
-
#
-
# ==== Options
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>method: symbol of HTTP verb</tt> - This modifier will dynamically
-
# create an HTML form and immediately submit the form for processing using
-
# the HTTP verb specified. Useful for having links perform a POST operation
-
# in dangerous actions like deleting a record (which search bots can follow
-
# while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
-
# Note that if the user has JavaScript disabled, the request will fall back
-
# to using GET. If <tt>href: '#'</tt> is used and the user has JavaScript
-
# disabled clicking the link will have no effect. If you are relying on the
-
# POST behavior, you should check for it in your controller's action by using
-
# the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
-
# * <tt>remote: true</tt> - This will allow the unobtrusive JavaScript
-
# driver to make an Ajax request to the URL in question instead of following
-
# the link. The drivers each provide mechanisms for listening for the
-
# completion of the Ajax request and performing JavaScript operations once
-
# they're complete
-
#
-
# ==== Data attributes
-
#
-
# * <tt>confirm: 'question?'</tt> - This will allow the unobtrusive JavaScript
-
# driver to prompt with the question specified. If the user accepts, the link is
-
# processed normally, otherwise no action is taken.
-
# * <tt>:disable_with</tt> - Value of this parameter will be
-
# used as the value for a disabled version of the submit
-
# button when the form is submitted. This feature is provided
-
# by the unobtrusive JavaScript driver.
-
#
-
# ==== Examples
-
# Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
-
# and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base
-
# your application on resources and use
-
#
-
# link_to "Profile", profile_path(@profile)
-
# # => <a href="/profiles/1">Profile</a>
-
#
-
# or the even pithier
-
#
-
# link_to "Profile", @profile
-
# # => <a href="/profiles/1">Profile</a>
-
#
-
# in place of the older more verbose, non-resource-oriented
-
#
-
# link_to "Profile", controller: "profiles", action: "show", id: @profile
-
# # => <a href="/profiles/show/1">Profile</a>
-
#
-
# Similarly,
-
#
-
# link_to "Profiles", profiles_path
-
# # => <a href="/profiles">Profiles</a>
-
#
-
# is better than
-
#
-
# link_to "Profiles", controller: "profiles"
-
# # => <a href="/profiles">Profiles</a>
-
#
-
# You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
-
#
-
# <%= link_to(@profile) do %>
-
# <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
-
# <% end %>
-
# # => <a href="/profiles/1">
-
# <strong>David</strong> -- <span>Check it out!</span>
-
# </a>
-
#
-
# Classes and ids for CSS are easy to produce:
-
#
-
# link_to "Articles", articles_path, id: "news", class: "article"
-
# # => <a href="/articles" class="article" id="news">Articles</a>
-
#
-
# Be careful when using the older argument style, as an extra literal hash is needed:
-
#
-
# link_to "Articles", { controller: "articles" }, id: "news", class: "article"
-
# # => <a href="/articles" class="article" id="news">Articles</a>
-
#
-
# Leaving the hash off gives the wrong link:
-
#
-
# link_to "WRONG!", controller: "articles", id: "news", class: "article"
-
# # => <a href="/articles/index/news?class=article">WRONG!</a>
-
#
-
# +link_to+ can also produce links with anchors or query strings:
-
#
-
# link_to "Comment wall", profile_path(@profile, anchor: "wall")
-
# # => <a href="/profiles/1#wall">Comment wall</a>
-
#
-
# link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails"
-
# # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>
-
#
-
# link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
-
# # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>
-
#
-
# The only option specific to +link_to+ (<tt>:method</tt>) is used as follows:
-
#
-
# link_to("Destroy", "http://www.example.com", method: :delete)
-
# # => <a href='http://www.example.com' rel="nofollow" data-method="delete">Destroy</a>
-
#
-
# You can also use custom data attributes using the <tt>:data</tt> option:
-
#
-
# link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
-
# # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?"">Visit Other Site</a>
-
1
def link_to(name = nil, options = nil, html_options = nil, &block)
-
54
html_options, options = options, name if block_given?
-
54
options ||= {}
-
-
54
html_options = convert_options_to_data_attributes(options, html_options)
-
-
54
url = url_for(options)
-
54
html_options['href'] ||= url
-
-
54
content_tag(:a, name || url, html_options, &block)
-
end
-
-
# Generates a form containing a single button that submits to the URL created
-
# by the set of +options+. This is the safest method to ensure links that
-
# cause changes to your data are not triggered by search bots or accelerators.
-
# If the HTML button does not work with your layout, you can also consider
-
# using the +link_to+ method with the <tt>:method</tt> modifier as described in
-
# the +link_to+ documentation.
-
#
-
# By default, the generated form element has a class name of <tt>button_to</tt>
-
# to allow styling of the form itself and its children. This can be changed
-
# using the <tt>:form_class</tt> modifier within +html_options+. You can control
-
# the form submission and input element behavior using +html_options+.
-
# This method accepts the <tt>:method</tt> modifier described in the +link_to+ documentation.
-
# If no <tt>:method</tt> modifier is given, it will default to performing a POST operation.
-
# You can also disable the button by passing <tt>disabled: true</tt> in +html_options+.
-
# If you are using RESTful routes, you can pass the <tt>:method</tt>
-
# to change the HTTP verb used to submit the form.
-
#
-
# ==== Options
-
# The +options+ hash accepts the same options as +url_for+.
-
#
-
# There are a few special +html_options+:
-
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
-
# <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
-
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
-
# submit behavior. By default this behavior is an ajax submit.
-
# * <tt>:form</tt> - This hash will be form attributes
-
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
-
# be placed
-
#
-
# ==== Data attributes
-
#
-
# * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
-
# prompt with the question specified. If the user accepts, the link is
-
# processed normally, otherwise no action is taken.
-
# * <tt>:disable_with</tt> - Value of this parameter will be
-
# used as the value for a disabled version of the submit
-
# button when the form is submitted. This feature is provided
-
# by the unobtrusive JavaScript driver.
-
#
-
# ==== Examples
-
# <%= button_to "New", action: "new" %>
-
# # => "<form method="post" action="/controller/new" class="button_to">
-
# # <div><input value="New" type="submit" /></div>
-
# # </form>"
-
#
-
# <%= button_to [:make_happy, @user] do %>
-
# Make happy <strong><%= @user.name %></strong>
-
# <% end %>
-
# # => "<form method="post" action="/users/1/make_happy" class="button_to">
-
# # <div>
-
# # <button type="submit">
-
# # Make happy <strong><%= @user.name %></strong>
-
# # </button>
-
# # </div>
-
# # </form>"
-
#
-
# <%= button_to "New", action: "new", form_class: "new-thing" %>
-
# # => "<form method="post" action="/controller/new" class="new-thing">
-
# # <div><input value="New" type="submit" /></div>
-
# # </form>"
-
#
-
#
-
# <%= button_to "Create", action: "create", remote: true, form: { "data-type" => "json" } %>
-
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
-
# # <div>
-
# # <input value="Create" type="submit" />
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
-
# # </div>
-
# # </form>"
-
#
-
#
-
# <%= button_to "Delete Image", { action: "delete", id: @image.id },
-
# method: :delete, data: { confirm: "Are you sure?" } %>
-
# # => "<form method="post" action="/images/delete/1" class="button_to">
-
# # <div>
-
# # <input type="hidden" name="_method" value="delete" />
-
# # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
-
# # </div>
-
# # </form>"
-
#
-
#
-
# <%= button_to('Destroy', 'http://www.example.com',
-
# method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
-
# # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
-
# # <div>
-
# # <input name='_method' value='delete' type='hidden' />
-
# # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
-
# # </div>
-
# # </form>"
-
# #
-
1
def button_to(name = nil, options = nil, html_options = nil, &block)
-
25
html_options, options = options, name if block_given?
-
25
options ||= {}
-
25
html_options ||= {}
-
-
25
html_options = html_options.stringify_keys
-
25
convert_boolean_attributes!(html_options, %w(disabled))
-
-
25
url = options.is_a?(String) ? options : url_for(options)
-
25
remote = html_options.delete('remote')
-
-
25
method = html_options.delete('method').to_s
-
25
method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe
-
-
25
form_method = method == 'get' ? 'get' : 'post'
-
25
form_options = html_options.delete('form') || {}
-
25
form_options[:class] ||= html_options.delete('form_class') || 'button_to'
-
25
form_options.merge!(method: form_method, action: url)
-
25
form_options.merge!("data-remote" => "true") if remote
-
-
25
request_token_tag = form_method == 'post' ? token_tag : ''
-
-
25
html_options = convert_options_to_data_attributes(options, html_options)
-
25
html_options['type'] = 'submit'
-
-
25
button = if block_given?
-
1
content_tag('button', html_options, &block)
-
else
-
24
html_options['value'] = name || url
-
24
tag('input', html_options)
-
end
-
-
25
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
-
25
content_tag('form', content_tag('div', inner_tags), form_options)
-
end
-
-
# Creates a link tag of the given +name+ using a URL created by the set of
-
# +options+ unless the current request URI is the same as the links, in
-
# which case only the name is returned (or the given block is yielded, if
-
# one exists). You can give +link_to_unless_current+ a block which will
-
# specialize the default behavior (e.g., show a "Start Here" link rather
-
# than the link's text).
-
#
-
# ==== Examples
-
# Let's say you have a navigation menu...
-
#
-
# <ul id="navbar">
-
# <li><%= link_to_unless_current("Home", { action: "index" }) %></li>
-
# <li><%= link_to_unless_current("About Us", { action: "about" }) %></li>
-
# </ul>
-
#
-
# If in the "about" action, it will render...
-
#
-
# <ul id="navbar">
-
# <li><a href="/controller/index">Home</a></li>
-
# <li>About Us</li>
-
# </ul>
-
#
-
# ...but if in the "index" action, it will render:
-
#
-
# <ul id="navbar">
-
# <li>Home</li>
-
# <li><a href="/controller/about">About Us</a></li>
-
# </ul>
-
#
-
# The implicit block given to +link_to_unless_current+ is evaluated if the current
-
# action is the action given. So, if we had a comments page and wanted to render a
-
# "Go Back" link instead of a link to the comments page, we could do something like this...
-
#
-
# <%=
-
# link_to_unless_current("Comment", { controller: "comments", action: "new" }) do
-
# link_to("Go back", { controller: "posts", action: "index" })
-
# end
-
# %>
-
1
def link_to_unless_current(name, options = {}, html_options = {}, &block)
-
16
link_to_unless current_page?(options), name, options, html_options, &block
-
end
-
-
# Creates a link tag of the given +name+ using a URL created by the set of
-
# +options+ unless +condition+ is true, in which case only the name is
-
# returned. To specialize the default behavior (i.e., show a login link rather
-
# than just the plaintext link text), you can pass a block that
-
# accepts the name or the full argument list for +link_to_unless+.
-
#
-
# ==== Examples
-
# <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %>
-
# # If the user is logged in...
-
# # => <a href="/controller/reply/">Reply</a>
-
#
-
# <%=
-
# link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name|
-
# link_to(name, { controller: "accounts", action: "signup" })
-
# end
-
# %>
-
# # If the user is logged in...
-
# # => <a href="/controller/reply/">Reply</a>
-
# # If not...
-
# # => <a href="/accounts/signup">Reply</a>
-
1
def link_to_unless(condition, name, options = {}, html_options = {}, &block)
-
24
if condition
-
14
if block_given?
-
2
block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
-
else
-
12
name
-
end
-
else
-
10
link_to(name, options, html_options)
-
end
-
end
-
-
# Creates a link tag of the given +name+ using a URL created by the set of
-
# +options+ if +condition+ is true, otherwise only the name is
-
# returned. To specialize the default behavior, you can pass a block that
-
# accepts the name or the full argument list for +link_to_unless+ (see the examples
-
# in +link_to_unless+).
-
#
-
# ==== Examples
-
# <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %>
-
# # If the user isn't logged in...
-
# # => <a href="/sessions/new/">Login</a>
-
#
-
# <%=
-
# link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do
-
# link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user })
-
# end
-
# %>
-
# # If the user isn't logged in...
-
# # => <a href="/sessions/new/">Login</a>
-
# # If they are logged in...
-
# # => <a href="/accounts/show/3">my_username</a>
-
1
def link_to_if(condition, name, options = {}, html_options = {}, &block)
-
3
link_to_unless !condition, name, options, html_options, &block
-
end
-
-
# Creates a mailto link tag to the specified +email_address+, which is
-
# also used as the name of the link unless +name+ is specified. Additional
-
# HTML attributes for the link can be passed in +html_options+.
-
#
-
# +mail_to+ has several methods for hindering email harvesters and customizing
-
# the email itself by passing special keys to +html_options+.
-
#
-
# ==== Options
-
# * <tt>:encode</tt> - This key will accept the strings "javascript" or "hex".
-
# Passing "javascript" will dynamically create and encode the mailto link then
-
# eval it into the DOM of the page. This method will not show the link on
-
# the page if the user has JavaScript disabled. Passing "hex" will hex
-
# encode the +email_address+ before outputting the mailto link.
-
# * <tt>:replace_at</tt> - When the link +name+ isn't provided, the
-
# +email_address+ is used for the link label. You can use this option to
-
# obfuscate the +email_address+ by substituting the @ sign with the string
-
# given as the value.
-
# * <tt>:replace_dot</tt> - When the link +name+ isn't provided, the
-
# +email_address+ is used for the link label. You can use this option to
-
# obfuscate the +email_address+ by substituting the . in the email with the
-
# string given as the value.
-
# * <tt>:subject</tt> - Preset the subject line of the email.
-
# * <tt>:body</tt> - Preset the body of the email.
-
# * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
-
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
-
#
-
# ==== Examples
-
# mail_to "me@domain.com"
-
# # => <a href="mailto:me@domain.com">me@domain.com</a>
-
#
-
# mail_to "me@domain.com", "My email", encode: "javascript"
-
# # => <script>eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
-
#
-
# mail_to "me@domain.com", "My email", encode: "hex"
-
# # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
-
#
-
# mail_to "me@domain.com", nil, replace_at: "_at_", replace_dot: "_dot_", class: "email"
-
# # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a>
-
#
-
# mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com",
-
# subject: "This is an example email"
-
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
-
1
def mail_to(email_address, name = nil, html_options = {})
-
20
email_address = ERB::Util.html_escape(email_address)
-
-
20
html_options = html_options.stringify_keys
-
20
encode = html_options.delete("encode").to_s
-
-
20
extras = %w{ cc bcc body subject }.map { |item|
-
80
option = html_options.delete(item) || next
-
4
"#{item}=#{Rack::Utils.escape_path(option)}"
-
}.compact
-
20
extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
-
-
20
email_address_obfuscated = email_address.to_str
-
20
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at")
-
20
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot")
-
20
case encode
-
when "javascript"
-
5
string = ''
-
5
html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))
-
5
html = escape_javascript(html.to_str)
-
5
"document.write('#{html}');".each_byte do |c|
-
343
string << sprintf("%%%x", c)
-
end
-
5
"<script>eval(decodeURIComponent('#{string}'))</script>".html_safe
-
when "hex"
-
6
email_address_encoded = email_address_obfuscated.unpack('C*').map {|c|
-
91
sprintf("&#%d;", c)
-
}.join
-
-
6
string = 'mailto:'.unpack('C*').map { |c|
-
42
sprintf("&#%d;", c)
-
}.join + email_address.unpack('C*').map { |c|
-
78
char = c.chr
-
78
char =~ /\w/ ? sprintf("%%%x", c) : char
-
}.join
-
-
6
content_tag "a", name || email_address_encoded.html_safe, html_options.merge("href" => "#{string}#{extras}".html_safe)
-
else
-
9
content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
-
end
-
end
-
-
# True if the current request URI was generated by the given +options+.
-
#
-
# ==== Examples
-
# Let's say we're in the <tt>/shop/checkout?order=desc</tt> action.
-
#
-
# current_page?(action: 'process')
-
# # => false
-
#
-
# current_page?(controller: 'shop', action: 'checkout')
-
# # => true
-
#
-
# current_page?(controller: 'shop', action: 'checkout', order: 'asc')
-
# # => false
-
#
-
# current_page?(action: 'checkout')
-
# # => true
-
#
-
# current_page?(controller: 'library', action: 'checkout')
-
# # => false
-
#
-
# Let's say we're in the <tt>/shop/checkout?order=desc&page=1</tt> action.
-
#
-
# current_page?(action: 'process')
-
# # => false
-
#
-
# current_page?(controller: 'shop', action: 'checkout')
-
# # => true
-
#
-
# current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1')
-
# # => true
-
#
-
# current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2')
-
# # => false
-
#
-
# current_page?(controller: 'shop', action: 'checkout', order: 'desc')
-
# # => false
-
#
-
# current_page?(action: 'checkout')
-
# # => true
-
#
-
# current_page?(controller: 'library', action: 'checkout')
-
# # => false
-
#
-
# Let's say we're in the <tt>/products</tt> action with method POST in case of invalid product.
-
#
-
# current_page?(controller: 'product', action: 'index')
-
# # => false
-
#
-
1
def current_page?(options)
-
23
unless request
-
raise "You cannot use helpers that need to determine the current " \
-
"page unless your view context provides a Request object " \
-
"in a #request method"
-
end
-
-
23
return false unless request.get?
-
-
22
url_string = url_for(options)
-
-
# We ignore any extra parameters in the request_uri if the
-
# submitted url doesn't have any either. This lets the function
-
# work with things like ?order=asc
-
22
request_uri = url_string.index("?") ? request.fullpath : request.path
-
-
22
if url_string =~ /^\w+:\/\//
-
11
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
-
else
-
11
url_string == request_uri
-
end
-
end
-
-
1
private
-
1
def convert_options_to_data_attributes(options, html_options)
-
79
if html_options
-
55
html_options = html_options.stringify_keys
-
55
html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
-
-
55
disable_with = html_options.delete("disable_with")
-
55
confirm = html_options.delete('confirm')
-
55
method = html_options.delete('method')
-
-
55
if confirm
-
7
message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
-
"Use 'data: { confirm: \'Text\' }' instead."
-
7
ActiveSupport::Deprecation.warn message
-
-
7
html_options["data-confirm"] = confirm
-
end
-
-
55
add_method_to_attributes!(html_options, method) if method
-
-
55
if disable_with
-
2
message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \
-
"Use 'data: { disable_with: \'Text\' }' instead."
-
2
ActiveSupport::Deprecation.warn message
-
-
2
html_options["data-disable-with"] = disable_with
-
end
-
-
55
html_options
-
else
-
24
link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
-
end
-
end
-
-
1
def link_to_remote_options?(options)
-
132
if options.is_a?(Hash)
-
63
options.delete('remote') || options.delete(:remote)
-
end
-
end
-
-
1
def add_method_to_attributes!(html_options, method)
-
8
if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
-
8
html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip
-
end
-
8
html_options["data-method"] = method
-
end
-
-
# Processes the +html_options+ hash, converting the boolean
-
# attributes from true/false form into the form required by
-
# HTML/XHTML. (An attribute is considered to be boolean if
-
# its name is listed in the given +bool_attrs+ array.)
-
#
-
# More specifically, for each boolean attribute in +html_options+
-
# given as:
-
#
-
# "attr" => bool_value
-
#
-
# if the associated +bool_value+ evaluates to true, it is
-
# replaced with the attribute's name; otherwise the attribute is
-
# removed from the +html_options+ hash. (See the XHTML 1.0 spec,
-
# section 4.5 "Attribute Minimization" for more:
-
# http://www.w3.org/TR/xhtml1/#h-4.5)
-
#
-
# Returns the updated +html_options+ hash, which is also modified
-
# in place.
-
#
-
# Example:
-
#
-
# convert_boolean_attributes!( html_options,
-
# %w( checked disabled readonly ) )
-
1
def convert_boolean_attributes!(html_options, bool_attrs)
-
50
bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
-
25
html_options
-
end
-
-
1
def token_tag(token=nil)
-
173
if token != false && protect_against_forgery?
-
41
token ||= form_authenticity_token
-
41
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
-
else
-
132
''
-
end
-
end
-
-
1
def method_tag(method)
-
88
tag('input', type: 'hidden', name: '_method', value: method.to_s)
-
end
-
end
-
end
-
end
-
1
module ActionView
-
# = Action View Log Subscriber
-
#
-
# Provides functionality so that Rails can output logs from Action View.
-
1
class LogSubscriber < ActiveSupport::LogSubscriber
-
1
VIEWS_PATTERN = /^app\/views\//.freeze
-
-
1
def render_template(event)
-
54
return unless logger.info?
-
54
message = " Rendered #{from_rails_root(event.payload[:identifier])}"
-
8
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
-
8
message << " (#{event.duration.round(1)}ms)"
-
8
info(message)
-
end
-
1
alias :render_partial :render_template
-
1
alias :render_collection :render_template
-
-
1
def logger
-
514
ActionView::Base.logger
-
end
-
-
1
protected
-
-
1
def from_rails_root(string)
-
54
string.sub("#{Rails.root}/", "").sub(VIEWS_PATTERN, "")
-
end
-
end
-
end
-
-
1
ActionView::LogSubscriber.attach_to :action_view
-
1
require 'active_support/core_ext/module/remove_method'
-
-
1
module ActionView
-
# = Action View Lookup Context
-
#
-
# LookupContext is the object responsible to hold all information required to lookup
-
# templates, i.e. view paths and details. The LookupContext is also responsible to
-
# generate a key, given to view paths, used in the resolver cache lookup. Since
-
# this key is generated just once during the request, it speeds up all cache accesses.
-
1
class LookupContext #:nodoc:
-
1
attr_accessor :prefixes, :rendered_format
-
-
1
mattr_accessor :fallbacks
-
1
@@fallbacks = FallbackFileSystemResolver.instances
-
-
1
mattr_accessor :registered_details
-
1
self.registered_details = []
-
-
1
def self.register_detail(name, options = {}, &block)
-
3
self.registered_details << name
-
9
initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
-
-
3
Accessors.send :define_method, :"default_#{name}", &block
-
3
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{name}
-
@details.fetch(:#{name}, [])
-
end
-
-
def #{name}=(value)
-
value = value.present? ? Array(value) : default_#{name}
-
_set_detail(:#{name}, value) if value != @details[:#{name}]
-
end
-
-
remove_possible_method :initialize_details
-
def initialize_details(details)
-
#{initialize.join("\n")}
-
end
-
METHOD
-
end
-
-
# Holds accessors for the registered details.
-
1
module Accessors #:nodoc:
-
end
-
-
1886
register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq }
-
3080
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
-
1868
register_detail(:handlers){ Template::Handlers.extensions }
-
-
1
class DetailsKey #:nodoc:
-
1
alias :eql? :equal?
-
1
alias :object_hash :hash
-
-
1
attr_reader :hash
-
1
@details_keys = Hash.new
-
-
1
def self.get(details)
-
749
@details_keys[details] ||= new
-
end
-
-
1
def self.clear
-
22
@details_keys.clear
-
end
-
-
1
def initialize
-
60
@hash = object_hash
-
end
-
end
-
-
# Add caching behavior on top of Details.
-
1
module DetailsCache
-
1
attr_accessor :cache
-
-
# Calculate the details key. Remove the handlers from calculation to improve performance
-
# since the user cannot modify it explicitly.
-
1
def details_key #:nodoc:
-
1122
@details_key ||= DetailsKey.get(@details) if @cache
-
end
-
-
# Temporary skip passing the details_key forward.
-
1
def disable_cache
-
13
old_value, @cache = @cache, false
-
13
yield
-
ensure
-
13
@cache = old_value
-
end
-
-
1
protected
-
-
1
def _set_detail(key, value)
-
1935
@details = @details.dup if @details_key
-
1935
@details_key = nil
-
1935
@details[key] = value
-
end
-
end
-
-
# Helpers related to template lookup using the lookup context information.
-
1
module ViewPaths
-
1
attr_reader :view_paths, :html_fallback_for_js
-
-
# Whenever setting view paths, makes a copy so we can manipulate then in
-
# instance objects as we wish.
-
1
def view_paths=(paths)
-
1869
@view_paths = ActionView::PathSet.new(Array(paths))
-
end
-
-
1
def find(name, prefixes = [], partial = false, keys = [], options = {})
-
861
@view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
-
end
-
1
alias :find_template :find
-
-
1
def find_all(name, prefixes = [], partial = false, keys = [], options = {})
-
270
@view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
-
end
-
-
1
def exists?(name, prefixes = [], partial = false, keys = [], options = {})
-
46
@view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
-
end
-
1
alias :template_exists? :exists?
-
-
# Add fallbacks to the view paths. Useful in cases you are rendering a :file.
-
1
def with_fallbacks
-
120
added_resolvers = 0
-
120
self.class.fallbacks.each do |resolver|
-
240
next if view_paths.include?(resolver)
-
238
view_paths.push(resolver)
-
238
added_resolvers += 1
-
end
-
120
yield
-
ensure
-
358
added_resolvers.times { view_paths.pop }
-
end
-
-
1
protected
-
-
1
def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc:
-
1177
name, prefixes = normalize_name(name, prefixes)
-
1177
details, details_key = detail_args_for(details_options)
-
1177
[name, prefixes, partial || false, details, details_key, keys]
-
end
-
-
# Compute details hash and key according to user options (e.g. passed from #render).
-
1
def detail_args_for(options)
-
1177
return @details, details_key if options.empty? # most common path.
-
60
user_details = @details.merge(options)
-
60
[user_details, DetailsKey.get(user_details)]
-
end
-
-
# Support legacy foo.erb names even though we now ignore .erb
-
# as well as incorrectly putting part of the path in the template
-
# name instead of the prefix.
-
1
def normalize_name(name, prefixes) #:nodoc:
-
1177
prefixes = prefixes.presence
-
1177
parts = name.to_s.split('/')
-
1177
parts.shift if parts.first.empty?
-
1177
name = parts.pop
-
-
1177
return name, prefixes || [""] if parts.empty?
-
-
702
parts = parts.join('/')
-
880
prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
-
-
702
return name, prefixes
-
end
-
end
-
-
1
include Accessors
-
1
include DetailsCache
-
1
include ViewPaths
-
-
1
def initialize(view_paths, details = {}, prefixes = [])
-
1867
@details, @details_key = {}, nil
-
1867
@skip_default_locale = false
-
1867
@cache = true
-
1867
@prefixes = prefixes
-
1867
@rendered_format = nil
-
-
1867
self.view_paths = view_paths
-
1867
initialize_details(details)
-
end
-
-
# Override formats= to expand ["*/*"] values and automatically
-
# add :html as fallback to :js.
-
1
def formats=(values)
-
3766
if values
-
2585
values.concat(default_formats) if values.delete "*/*"
-
2585
if values == [:js]
-
26
values << :html
-
26
@html_fallback_for_js = true
-
end
-
end
-
3766
super(values)
-
end
-
-
# Do not use the default locale on template lookup.
-
1
def skip_default_locale!
-
2
@skip_default_locale = true
-
2
self.locale = nil
-
end
-
-
# Override locale to return a symbol instead of array.
-
1
def locale
-
7
@details[:locale].first
-
end
-
-
# Overload locale= to also set the I18n.locale. If the current I18n.config object responds
-
# to original_config, it means that it's has a copy of the original I18n configuration and it's
-
# acting as proxy, which we need to skip.
-
1
def locale=(value)
-
20
if value
-
18
config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
-
18
config.locale = value
-
end
-
-
20
super(@skip_default_locale ? I18n.locale : default_locale)
-
end
-
-
# A method which only uses the first format in the formats array for layout lookup.
-
1
def with_layout_format
-
309
if formats.size == 1
-
222
yield
-
else
-
87
old_formats = formats
-
87
_set_detail(:formats, formats[0,1])
-
-
87
begin
-
87
yield
-
ensure
-
87
_set_detail(:formats, old_formats)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module ModelNaming
-
# Converts the given object to an ActiveModel compliant one.
-
1
def convert_to_model(object)
-
929
object.respond_to?(:to_model) ? object.to_model : object
-
end
-
-
1
def model_name_from_record_or_class(record_or_class)
-
377
(record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
-
end
-
end
-
end
-
1
module ActionView #:nodoc:
-
# = Action View PathSet
-
1
class PathSet #:nodoc:
-
1
include Enumerable
-
-
1
attr_reader :paths
-
-
1
delegate :[], :include?, :pop, :size, :each, to: :paths
-
-
1
def initialize(paths = [])
-
2080
@paths = typecast paths
-
end
-
-
1
def initialize_copy(other)
-
1
@paths = other.paths.dup
-
1
self
-
end
-
-
1
def to_ary
-
1812
paths.dup
-
end
-
-
1
def compact
-
56
PathSet.new paths.compact
-
end
-
-
1
def +(array)
-
3
PathSet.new(paths + array)
-
end
-
-
1
%w(<< concat push insert unshift).each do |method|
-
5
class_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{method}(*args)
-
paths.#{method}(*typecast(args))
-
end
-
METHOD
-
end
-
-
1
def find(*args)
-
862
find_all(*args).first || raise(MissingTemplate.new(self, *args))
-
end
-
-
1
def find_all(path, prefixes = [], *args)
-
1181
prefixes = [prefixes] if String === prefixes
-
1181
prefixes.each do |prefix|
-
1188
paths.each do |resolver|
-
1281
templates = resolver.find_all(path, prefix, *args)
-
1281
return templates unless templates.empty?
-
end
-
end
-
309
[]
-
end
-
-
1
def exists?(path, prefixes, *args)
-
46
find_all(path, prefixes, *args).any?
-
end
-
-
1
private
-
-
1
def typecast(paths)
-
2331
paths.map do |path|
-
2327
case path
-
when Pathname, String
-
69
OptimizedFileSystemResolver.new path.to_s
-
else
-
2258
path
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module'
-
1
require 'action_view/model_naming'
-
-
1
module ActionView
-
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
-
# pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
-
# a higher logical level.
-
#
-
# # routes
-
# resources :posts
-
#
-
# # view
-
# <%= div_for(post) do %> <div id="post_45" class="post">
-
# <%= post.body %> What a wonderful world!
-
# <% end %> </div>
-
#
-
# # controller
-
# def update
-
# post = Post.find(params[:id])
-
# post.update_attributes(params[:post])
-
#
-
# redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
-
# end
-
#
-
# As the example above shows, you can stop caring to a large extent what the actual id of the post is.
-
# You just know that one is being assigned and that the subsequent calls in redirect_to expect that
-
# same naming convention and allows you to write less code if you follow it.
-
1
module RecordIdentifier
-
1
extend self
-
1
extend ModelNaming
-
-
1
include ModelNaming
-
-
1
JOIN = '_'.freeze
-
1
NEW = 'new'.freeze
-
-
# The DOM class convention is to use the singular form of an object or class.
-
#
-
# dom_class(post) # => "post"
-
# dom_class(Person) # => "person"
-
#
-
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
-
#
-
# dom_class(post, :edit) # => "edit_post"
-
# dom_class(Person, :edit) # => "edit_person"
-
1
def dom_class(record_or_class, prefix = nil)
-
204
singular = model_name_from_record_or_class(record_or_class).param_key
-
204
prefix ? "#{prefix}#{JOIN}#{singular}" : singular
-
end
-
-
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
-
# If no id is found, prefix with "new_" instead.
-
#
-
# dom_id(Post.find(45)) # => "post_45"
-
# dom_id(Post.new) # => "new_post"
-
#
-
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
-
#
-
# dom_id(Post.find(45), :edit) # => "edit_post_45"
-
# dom_id(Post.new, :custom) # => "custom_post"
-
1
def dom_id(record, prefix = nil)
-
103
if record_id = record_key_for_dom_id(record)
-
93
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
-
else
-
10
dom_class(record, prefix || NEW)
-
end
-
end
-
-
1
protected
-
-
# Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
-
# This can be overwritten to customize the default generated string representation if desired.
-
# If you need to read back a key from a dom_id in order to query for the underlying database record,
-
# you should write a helper like 'person_record_from_dom_id' that will extract the key either based
-
# on the default implementation (which just joins all key attributes with '_') or on your own
-
# overwritten version of the method. By default, this implementation passes the key string through a
-
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
-
# make sure yourself that your dom ids are valid, in case you overwrite this method.
-
1
def record_key_for_dom_id(record)
-
103
key = convert_to_model(record).to_key
-
103
key ? key.join('_') : key
-
end
-
end
-
end
-
1
module ActionView
-
1
class AbstractRenderer #:nodoc:
-
1
delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
-
-
1
def initialize(lookup_context)
-
1287
@lookup_context = lookup_context
-
end
-
-
1
def render
-
raise NotImplementedError
-
end
-
-
1
protected
-
-
1
def extract_details(options)
-
1466
@lookup_context.registered_details.each_with_object({}) do |key, details|
-
4398
next unless value = options[key]
-
54
details[key] = Array(value)
-
end
-
end
-
-
1
def instrument(name, options={})
-
2806
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
-
end
-
-
1
def prepend_formats(formats)
-
1423
formats = Array(formats)
-
1423
return if formats.empty? || @lookup_context.html_fallback_for_js
-
1013
@lookup_context.formats = formats | @lookup_context.formats
-
end
-
end
-
end
-
-
1
module ActionView
-
# = Action View Partials
-
#
-
# There's also a convenience method for rendering sub templates within the current controller that depends on a
-
# single object (we call this kind of sub templates for partials). It relies on the fact that partials should
-
# follow the naming convention of being prefixed with an underscore -- as to separate them from regular
-
# templates that could be rendered on their own.
-
#
-
# In a template for Advertiser#account:
-
#
-
# <%= render partial: "account" %>
-
#
-
# This would render "advertiser/_account.html.erb".
-
#
-
# In another template for Advertiser#buy, we could have:
-
#
-
# <%= render partial: "account", locals: { account: @buyer } %>
-
#
-
# <% @advertisements.each do |ad| %>
-
# <%= render partial: "ad", locals: { ad: ad } %>
-
# <% end %>
-
#
-
# This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
-
# render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
-
#
-
# == The :as and :object options
-
#
-
# By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables.
-
# The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
-
#
-
# <%= render partial: "account", object: @buyer %>
-
#
-
# would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is
-
# equivalent to:
-
#
-
# <%= render partial: "account", locals: { account: @buyer } %>
-
#
-
# With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
-
# wanted it to be +user+ instead of +account+ we'd do:
-
#
-
# <%= render partial: "account", object: @buyer, as: 'user' %>
-
#
-
# This is equivalent to
-
#
-
# <%= render partial: "account", locals: { user: @buyer } %>
-
#
-
# == Rendering a collection of partials
-
#
-
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and
-
# render a sub template for each of the elements. This pattern has been implemented as a single method that
-
# accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
-
# example in "Using partials" can be rewritten with a single line:
-
#
-
# <%= render partial: "ad", collection: @advertisements %>
-
#
-
# This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
-
# iteration counter will automatically be made available to the template with a name of the form
-
# +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
-
#
-
# The <tt>:as</tt> option may be used when rendering partials.
-
#
-
# You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
-
# The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
-
#
-
# <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
-
#
-
# If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
-
# to specify a text which will displayed instead by using this form:
-
#
-
# <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
-
#
-
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
-
# just keep domain objects, like Active Records, in there.
-
#
-
# == Rendering shared partials
-
#
-
# Two controllers can share a set of partials and render them like this:
-
#
-
# <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
-
#
-
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
-
#
-
# == Rendering objects that respond to `to_partial_path`
-
#
-
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
-
# and pick the proper path by checking `to_partial_path` method.
-
#
-
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
-
# # <%= render partial: "accounts/account", locals: { account: @account} %>
-
# <%= render partial: @account %>
-
#
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
-
# # that's why we can replace:
-
# # <%= render partial: "posts/post", collection: @posts %>
-
# <%= render partial: @posts %>
-
#
-
# == Rendering the default case
-
#
-
# If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
-
# defaults of render to render partials. Examples:
-
#
-
# # Instead of <%= render partial: "account" %>
-
# <%= render "account" %>
-
#
-
# # Instead of <%= render partial: "account", locals: { account: @buyer } %>
-
# <%= render "account", account: @buyer %>
-
#
-
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
-
# # <%= render partial: "accounts/account", locals: { account: @account} %>
-
# <%= render @account %>
-
#
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
-
# # that's why we can replace:
-
# # <%= render partial: "posts/post", collection: @posts %>
-
# <%= render @posts %>
-
#
-
# == Rendering partials with layouts
-
#
-
# Partials can have their own layouts applied to them. These layouts are different than the ones that are
-
# specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
-
# of users:
-
#
-
# <%# app/views/users/index.html.erb &>
-
# Here's the administrator:
-
# <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
-
#
-
# Here's the editor:
-
# <%= render partial: "user", layout: "editor", locals: { user: editor } %>
-
#
-
# <%# app/views/users/_user.html.erb &>
-
# Name: <%= user.name %>
-
#
-
# <%# app/views/users/_administrator.html.erb &>
-
# <div id="administrator">
-
# Budget: $<%= user.budget %>
-
# <%= yield %>
-
# </div>
-
#
-
# <%# app/views/users/_editor.html.erb &>
-
# <div id="editor">
-
# Deadline: <%= user.deadline %>
-
# <%= yield %>
-
# </div>
-
#
-
# ...this will return:
-
#
-
# Here's the administrator:
-
# <div id="administrator">
-
# Budget: $<%= user.budget %>
-
# Name: <%= user.name %>
-
# </div>
-
#
-
# Here's the editor:
-
# <div id="editor">
-
# Deadline: <%= user.deadline %>
-
# Name: <%= user.name %>
-
# </div>
-
#
-
# If a collection is given, the layout will be rendered once for each item in
-
# the collection. Just think these two snippets have the same output:
-
#
-
# <%# app/views/users/_user.html.erb %>
-
# Name: <%= user.name %>
-
#
-
# <%# app/views/users/index.html.erb %>
-
# <%# This does not use layouts %>
-
# <ul>
-
# <% users.each do |user| -%>
-
# <li>
-
# <%= render partial: "user", locals: { user: user } %>
-
# </li>
-
# <% end -%>
-
# </ul>
-
#
-
# <%# app/views/users/_li_layout.html.erb %>
-
# <li>
-
# <%= yield %>
-
# </li>
-
#
-
# <%# app/views/users/index.html.erb %>
-
# <ul>
-
# <%= render partial: "user", layout: "li_layout", collection: users %>
-
# </ul>
-
#
-
# Given two users whose names are Alice and Bob, these snippets return:
-
#
-
# <ul>
-
# <li>
-
# Name: Alice
-
# </li>
-
# <li>
-
# Name: Bob
-
# </li>
-
# </ul>
-
#
-
# The current object being rendered, as well as the object_counter, will be
-
# available as local variables inside the layout template under the same names
-
# as available in the partial.
-
#
-
# You can also apply a layout to a block within any template:
-
#
-
# <%# app/views/users/_chief.html.erb &>
-
# <%= render(layout: "administrator", locals: { user: chief }) do %>
-
# Title: <%= chief.title %>
-
# <% end %>
-
#
-
# ...this will return:
-
#
-
# <div id="administrator">
-
# Budget: $<%= user.budget %>
-
# Title: <%= chief.name %>
-
# </div>
-
#
-
# As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
-
#
-
# If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
-
# an array to layout and treat it as an enumerable.
-
#
-
# <%# app/views/users/_user.html.erb &>
-
# <div class="user">
-
# Budget: $<%= user.budget %>
-
# <%= yield user %>
-
# </div>
-
#
-
# <%# app/views/users/index.html.erb &>
-
# <%= render layout: @users do |user| %>
-
# Title: <%= user.title %>
-
# <% end %>
-
#
-
# This will render the layout for each user and yield to the block, passing the user, each time.
-
#
-
# You can also yield multiple times in one layout and use block arguments to differentiate the sections.
-
#
-
# <%# app/views/users/_user.html.erb &>
-
# <div class="user">
-
# <%= yield user, :header %>
-
# Budget: $<%= user.budget %>
-
# <%= yield user, :footer %>
-
# </div>
-
#
-
# <%# app/views/users/index.html.erb &>
-
# <%= render layout: @users do |user, section| %>
-
# <%- case section when :header -%>
-
# Title: <%= user.title %>
-
# <%- when :footer -%>
-
# Deadline: <%= user.deadline %>
-
# <%- end -%>
-
# <% end %>
-
1
class PartialRenderer < AbstractRenderer
-
4
PREFIXED_PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
-
-
1
def initialize(*)
-
177
super
-
177
@context_prefix = @lookup_context.prefixes.first
-
end
-
-
1
def render(context, options, block)
-
209
setup(context, options, block)
-
201
identifier = (@template = find_partial) ? @template.identifier : @path
-
-
196
@lookup_context.rendered_format ||= begin
-
122
if @template && @template.formats.present?
-
118
@template.formats.first
-
else
-
4
formats.first
-
end
-
end
-
-
196
if @collection
-
44
instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
-
44
render_collection
-
end
-
else
-
152
instrument(:partial, :identifier => identifier) do
-
152
render_partial
-
end
-
end
-
end
-
-
1
def render_collection
-
44
return nil if @collection.blank?
-
-
37
if @options.key?(:spacer_template)
-
1
spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
-
end
-
-
37
result = @template ? collection_with_template : collection_without_template
-
37
result.join(spacer).html_safe
-
end
-
-
1
def render_partial
-
152
view, locals, block = @view, @locals, @block
-
152
object, as = @object, @variable
-
-
152
if !block && (layout = @options[:layout])
-
21
layout = find_template(layout, @template_keys)
-
end
-
-
152
object ||= locals[as]
-
152
locals[as] = object
-
-
152
content = @template.render(view, locals) do |*name|
-
27
view._layout_for(*name, &block)
-
end
-
-
166
content = layout.render(view, locals){ content } if layout
-
145
content
-
end
-
-
1
private
-
-
1
def setup(context, options, block)
-
209
@view = context
-
209
partial = options[:partial]
-
-
209
@options = options
-
209
@locals = options[:locals] || {}
-
209
@block = block
-
209
@details = extract_details(options)
-
-
209
prepend_formats(options[:formats])
-
-
209
if String === partial
-
191
@object = options[:object]
-
191
@path = partial
-
191
@collection = collection
-
else
-
18
@object = partial
-
-
18
if @collection = collection_from_object || collection
-
24
paths = @collection_data = @collection.map { |o| partial_path(o) }
-
8
@path = paths.uniq.size == 1 ? paths.first : nil
-
else
-
10
@path = partial_path
-
end
-
end
-
-
207
if as = options[:as]
-
6
raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/
-
6
as = as.to_sym
-
end
-
-
207
if @path
-
203
@variable, @variable_counter = retrieve_variable(@path, as)
-
197
@template_keys = retrieve_template_keys
-
else
-
12
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
-
end
-
-
201
self
-
end
-
-
1
def collection
-
201
if @options.key?(:collection)
-
36
collection = @options[:collection]
-
36
collection.respond_to?(:to_ary) ? collection.to_ary : []
-
end
-
end
-
-
1
def collection_from_object
-
18
@object.to_ary if @object.respond_to?(:to_ary)
-
end
-
-
1
def find_partial
-
201
if path = @path
-
197
find_template(path, @template_keys)
-
end
-
end
-
-
1
def find_template(path, locals)
-
229
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
-
229
@lookup_context.find_template(path, prefixes, true, locals, @details)
-
end
-
-
1
def collection_with_template
-
35
view, locals, template = @view, @locals, @template
-
35
as, counter = @variable, @variable_counter
-
-
35
if layout = @options[:layout]
-
6
layout = find_template(layout, @template_keys)
-
end
-
-
35
index = -1
-
35
@collection.map do |object|
-
68
locals[as] = object
-
68
locals[counter] = (index += 1)
-
-
68
content = template.render(view, locals)
-
80
content = layout.render(view, locals) { content } if layout
-
68
content
-
end
-
end
-
-
1
def collection_without_template
-
2
view, locals, collection_data = @view, @locals, @collection_data
-
2
cache = {}
-
2
keys = @locals.keys
-
-
2
index = -1
-
2
@collection.map do |object|
-
8
index += 1
-
8
path, as, counter = collection_data[index]
-
-
8
locals[as] = object
-
8
locals[counter] = index
-
-
8
template = (cache[path] ||= find_template(path, keys + [as, counter]))
-
8
template.render(view, locals)
-
end
-
end
-
-
1
def partial_path(object = @object)
-
26
object = object.to_model if object.respond_to?(:to_model)
-
-
26
path = if object.respond_to?(:to_partial_path)
-
24
object.to_partial_path
-
else
-
2
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
-
end
-
-
24
if @view.prefix_partial_path_with_controller_namespace
-
24
prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
-
else
-
path
-
end
-
end
-
-
1
def prefixed_partial_names
-
24
@prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
-
end
-
-
1
def merge_prefix_into_object_path(prefix, object_path)
-
8
if prefix.include?(?/) && object_path.include?(?/)
-
1
prefixes = []
-
1
prefix_array = File.dirname(prefix).split('/')
-
1
object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
-
-
1
prefix_array.each_with_index do |dir, index|
-
1
break if dir == object_path_array[index]
-
prefixes << dir
-
end
-
-
1
(prefixes << object_path).join("/")
-
else
-
7
object_path
-
end
-
end
-
-
1
def retrieve_template_keys
-
197
keys = @locals.keys
-
197
keys << @variable
-
197
keys << @variable_counter if @collection
-
197
keys
-
end
-
-
1
def retrieve_variable(path, as)
-
211
variable = as || begin
-
205
base = path[-1] == "/" ? "" : File.basename(path)
-
205
raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
-
199
$1.to_sym
-
end
-
205
variable_counter = :"#{variable}_counter" if @collection
-
205
[variable, variable_counter]
-
end
-
-
1
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
-
"make sure your partial name starts with a lowercase letter or underscore, " +
-
"and is followed by any combination of letters, numbers and underscores."
-
-
1
def raise_invalid_identifier(path)
-
6
raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
-
end
-
end
-
end
-
1
module ActionView
-
# This is the main entry point for rendering. It basically delegates
-
# to other objects like TemplateRenderer and PartialRenderer which
-
# actually renders the template.
-
1
class Renderer
-
1
attr_accessor :lookup_context
-
-
1
def initialize(lookup_context)
-
1485
@lookup_context = lookup_context
-
end
-
-
# Main render entry point shared by AV and AC.
-
1
def render(context, options)
-
1397
if options.key?(:partial)
-
162
render_partial(context, options)
-
else
-
1235
render_template(context, options)
-
end
-
end
-
-
# Render but returns a valid Rack body. If fibers are defined, we return
-
# a streaming body that renders the template piece by piece.
-
#
-
# Note that partials are not supported to be rendered with streaming,
-
# so in such cases, we just wrap them in an array.
-
1
def render_body(context, options)
-
23
if options.key?(:partial)
-
1
[render_partial(context, options)]
-
else
-
22
StreamingTemplateRenderer.new(@lookup_context).render(context, options)
-
end
-
end
-
-
# Direct accessor to template rendering.
-
1
def render_template(context, options) #:nodoc:
-
1235
_template_renderer.render(context, options)
-
end
-
-
# Direct access to partial rendering.
-
1
def render_partial(context, options, &block) #:nodoc:
-
209
_partial_renderer.render(context, options, block)
-
end
-
-
1
private
-
-
1
def _template_renderer #:nodoc:
-
1235
@_template_renderer ||= TemplateRenderer.new(@lookup_context)
-
end
-
-
1
def _partial_renderer #:nodoc:
-
209
@_partial_renderer ||= PartialRenderer.new(@lookup_context)
-
end
-
end
-
end
-
1
require 'fiber'
-
-
1
module ActionView
-
# == TODO
-
#
-
# * Support streaming from child templates, partials and so on.
-
# * Integrate exceptions with exceptron
-
# * Rack::Cache needs to support streaming bodies
-
1
class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
-
# A valid Rack::Body (i.e. it responds to each).
-
# It is initialized with a block that, when called, starts
-
# rendering the template.
-
1
class Body #:nodoc:
-
1
def initialize(&start)
-
15
@start = start
-
end
-
-
1
def each(&block)
-
15
begin
-
15
@start.call(block)
-
rescue Exception => exception
-
3
log_error(exception)
-
3
block.call ActionView::Base.streaming_completion_on_exception
-
end
-
15
self
-
end
-
-
1
private
-
-
# This is the same logging logic as in ShowExceptions middleware.
-
# TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
-
1
def log_error(exception) #:nodoc:
-
3
logger = ActionView::Base.logger
-
3
return unless logger
-
-
1
message = "\n#{exception.class} (#{exception.message}):\n"
-
1
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
-
1
message << " " << exception.backtrace.join("\n ")
-
1
logger.fatal("#{message}\n\n")
-
end
-
end
-
-
# For streaming, instead of rendering a given a template, we return a Body
-
# object that responds to each. This object is initialized with a block
-
# that knows how to render the template.
-
1
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
-
22
return [super] unless layout_name && template.supports_streaming?
-
-
15
locals ||= {}
-
15
layout = layout_name && find_layout(layout_name, locals.keys)
-
-
15
Body.new do |buffer|
-
15
delayed_render(buffer, template, layout, @view, locals)
-
end
-
end
-
-
1
private
-
-
1
def delayed_render(buffer, template, layout, view, locals)
-
# Wrap the given buffer in the StreamingBuffer and pass it to the
-
# underlying template handler. Now, everytime something is concatenated
-
# to the buffer, it is not appended to an array, but streamed straight
-
# to the client.
-
15
output = ActionView::StreamingBuffer.new(buffer)
-
42
yielder = lambda { |*name| view._layout_for(*name) }
-
-
15
instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
-
15
fiber = Fiber.new do
-
15
if layout
-
15
layout.render(view, locals, output, &yielder)
-
else
-
# If you don't have a layout, just render the thing
-
# and concatenate the final result. This is the same
-
# as a layout with just <%= yield %>
-
output.safe_concat view._layout_for
-
end
-
end
-
-
# Set the view flow to support streaming. It will be aware
-
# when to stop rendering the layout because it needs to search
-
# something in the template and vice-versa.
-
15
view.view_flow = StreamingFlow.new(view, fiber)
-
-
# Yo! Start the fiber!
-
15
fiber.resume
-
-
# If the fiber is still alive, it means we need something
-
# from the template, so start rendering it. If not, it means
-
# the layout exited without requiring anything from the template.
-
14
if fiber.alive?
-
14
content = template.render(view, locals, &yielder)
-
-
# Once rendering the template is done, sets its content in the :layout key.
-
12
view.view_flow.set(:layout, content)
-
-
# In case the layout continues yielding, we need to resume
-
# the fiber until all yields are handled.
-
12
fiber.resume while fiber.alive?
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/try'
-
-
1
module ActionView
-
1
class TemplateRenderer < AbstractRenderer #:nodoc:
-
1
def render(context, options)
-
1257
@view = context
-
1257
@details = extract_details(options)
-
1257
template = determine_template(options)
-
1214
context = @lookup_context
-
-
1214
prepend_formats(template.formats)
-
-
1214
unless context.rendered_format
-
1022
context.rendered_format = template.formats.first || formats.last
-
end
-
-
1214
render_template(template, options[:layout], options[:locals])
-
end
-
-
# Determine the template to be rendered using the given options.
-
1
def determine_template(options) #:nodoc:
-
1257
keys = options.fetch(:locals, {}).keys
-
-
1257
if options.key?(:text)
-
530
Template::Text.new(options[:text], formats.first)
-
727
elsif options.key?(:file)
-
232
with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
-
611
elsif options.key?(:inline)
-
210
handler = Template.handler_for_extension(options[:type] || "erb")
-
210
Template.new(options[:inline], "inline template", handler, :locals => keys)
-
401
elsif options.key?(:template)
-
399
if options[:template].respond_to?(:render)
-
24
options[:template]
-
else
-
375
find_template(options[:template], options[:prefixes], false, keys, @details)
-
end
-
else
-
2
raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option."
-
end
-
end
-
-
# Renders the given template. A string representing the layout can be
-
# supplied as well.
-
1
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
-
1199
view, locals = @view, locals || {}
-
-
1199
render_with_layout(layout_name, locals) do |layout|
-
1192
instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
-
1198
template.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
end
-
end
-
-
1
def render_with_layout(path, locals) #:nodoc:
-
1199
layout = path && find_layout(path, locals.keys)
-
1192
content = yield(layout)
-
-
1181
if layout
-
114
view = @view
-
114
view.view_flow.set(:layout, content)
-
234
layout.render(view, locals){ |*name| view._layout_for(*name) }
-
else
-
1067
content
-
end
-
end
-
-
# This is the method which actually finds the layout using details in the lookup
-
# context object. If no layout is found, it checks if at least a layout with
-
# the given name exists across all details before raising the error.
-
1
def find_layout(layout, keys)
-
618
with_layout_format { resolve_layout(layout, keys) }
-
end
-
-
1
def resolve_layout(layout, keys)
-
549
case layout
-
when String
-
104
begin
-
104
if layout =~ /^\//
-
2
with_fallbacks { find_template(layout, nil, false, keys, @details) }
-
else
-
103
find_template(layout, nil, false, keys, @details)
-
end
-
5
rescue ActionView::MissingTemplate
-
5
all_details = @details.merge(:formats => @lookup_context.default_formats)
-
5
raise unless template_exists?(layout, nil, false, keys, all_details)
-
end
-
when Proc
-
243
resolve_layout(layout.call, keys)
-
when FalseClass
-
nil
-
else
-
201
layout
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module RoutingUrlFor
-
-
# Returns the URL for the set of +options+ provided. This takes the
-
# same options as +url_for+ in Action Controller (see the
-
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
-
# <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action"
-
# instead of the fully qualified URL like "http://example.com/controller/action".
-
#
-
# ==== Options
-
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
-
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
-
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
-
# is currently not recommended since it breaks caching.
-
# * <tt>:host</tt> - Overrides the default (current) host if provided.
-
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
-
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
-
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
-
#
-
# ==== Relying on named routes
-
#
-
# Passing a record (like an Active Record) instead of a hash as the options parameter will
-
# trigger the named route for that record. The lookup will happen on the name of the class. So passing a
-
# Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
-
# +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
-
#
-
# ==== Implicit Controller Namespacing
-
#
-
# Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one.
-
#
-
# ==== Examples
-
# <%= url_for(action: 'index') %>
-
# # => /blog/
-
#
-
# <%= url_for(action: 'find', controller: 'books') %>
-
# # => /books/find
-
#
-
# <%= url_for(action: 'login', controller: 'members', only_path: false, protocol: 'https') %>
-
# # => https://www.example.com/members/login/
-
#
-
# <%= url_for(action: 'play', anchor: 'player') %>
-
# # => /messages/play/#player
-
#
-
# <%= url_for(action: 'jump', anchor: 'tax&ship') %>
-
# # => /testing/jump/#tax&ship
-
#
-
# <%= url_for(Workshop.new) %>
-
# # relies on Workshop answering a persisted? call (and in this case returning false)
-
# # => /workshops
-
#
-
# <%= url_for(@workshop) %>
-
# # calls @workshop.to_param which by default returns the id
-
# # => /workshops/5
-
#
-
# # to_param can be re-defined in a model to provide different URL names:
-
# # => /workshops/1-workshop-name
-
#
-
# <%= url_for("http://www.example.com") %>
-
# # => http://www.example.com
-
#
-
# <%= url_for(:back) %>
-
# # if request.env["HTTP_REFERER"] is set to "http://www.example.com"
-
# # => http://www.example.com
-
#
-
# <%= url_for(:back) %>
-
# # if request.env["HTTP_REFERER"] is not set or is blank
-
# # => javascript:history.back()
-
#
-
# <%= url_for(action: 'index', controller: 'users') %>
-
# # Assuming an "admin" namespace
-
# # => /admin/users
-
#
-
# <%= url_for(action: 'index', controller: '/users') %>
-
# # Specify absolute path with beginning slash
-
# # => /users
-
1
def url_for(options = nil)
-
318
case options
-
when String
-
176
options
-
when nil, Hash
-
128
options ||= {}
-
128
options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys)
-
128
super
-
when :back
-
4
_back_url
-
else
-
10
polymorphic_path(options)
-
end
-
end
-
-
1
def url_options #:nodoc:
-
180
return super unless controller.respond_to?(:url_options)
-
158
controller.url_options
-
end
-
-
1
def _routes_context #:nodoc:
-
controller
-
end
-
1
protected :_routes_context
-
-
1
def optimize_routes_generation? #:nodoc:
-
53
controller.respond_to?(:optimize_routes_generation?, true) ?
-
1
controller.optimize_routes_generation? : super
-
end
-
1
protected :optimize_routes_generation?
-
end
-
end
-
1
require 'active_support/core_ext/object/try'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/deprecation'
-
1
require 'thread'
-
-
1
module ActionView
-
# = Action View Template
-
1
class Template
-
1
extend ActiveSupport::Autoload
-
-
# === Encodings in ActionView::Template
-
#
-
# ActionView::Template is one of a few sources of potential
-
# encoding issues in Rails. This is because the source for
-
# templates are usually read from disk, and Ruby (like most
-
# encoding-aware programming languages) assumes that the
-
# String retrieved through File IO is encoded in the
-
# <tt>default_external</tt> encoding. In Rails, the default
-
# <tt>default_external</tt> encoding is UTF-8.
-
#
-
# As a result, if a user saves their template as ISO-8859-1
-
# (for instance, using a non-Unicode-aware text editor),
-
# and uses characters outside of the ASCII range, their
-
# users will see diamonds with question marks in them in
-
# the browser.
-
#
-
# For the rest of this documentation, when we say "UTF-8",
-
# we mean "UTF-8 or whatever the default_internal encoding
-
# is set to". By default, it will be UTF-8.
-
#
-
# To mitigate this problem, we use a few strategies:
-
# 1. If the source is not valid UTF-8, we raise an exception
-
# when the template is compiled to alert the user
-
# to the problem.
-
# 2. The user can specify the encoding using Ruby-style
-
# encoding comments in any template engine. If such
-
# a comment is supplied, Rails will apply that encoding
-
# to the resulting compiled source returned by the
-
# template handler.
-
# 3. In all cases, we transcode the resulting String to
-
# the UTF-8.
-
#
-
# This means that other parts of Rails can always assume
-
# that templates are encoded in UTF-8, even if the original
-
# source of the template was not UTF-8.
-
#
-
# From a user's perspective, the easiest thing to do is
-
# to save your templates as UTF-8. If you do this, you
-
# do not need to do anything else for things to "just work".
-
#
-
# === Instructions for template handlers
-
#
-
# The easiest thing for you to do is to simply ignore
-
# encodings. Rails will hand you the template source
-
# as the default_internal (generally UTF-8), raising
-
# an exception for the user before sending the template
-
# to you if it could not determine the original encoding.
-
#
-
# For the greatest simplicity, you can support only
-
# UTF-8 as the <tt>default_internal</tt>. This means
-
# that from the perspective of your handler, the
-
# entire pipeline is just UTF-8.
-
#
-
# === Advanced: Handlers with alternate metadata sources
-
#
-
# If you want to provide an alternate mechanism for
-
# specifying encodings (like ERB does via <%# encoding: ... %>),
-
# you may indicate that you will handle encodings yourself
-
# by implementing <tt>self.handles_encoding?</tt>
-
# on your handler.
-
#
-
# If you do, Rails will not try to encode the String
-
# into the default_internal, passing you the unaltered
-
# bytes tagged with the assumed encoding (from
-
# default_external).
-
#
-
# In this case, make sure you return a String from
-
# your handler encoded in the default_internal. Since
-
# you are handling out-of-band metadata, you are
-
# also responsible for alerting the user to any
-
# problems with converting the user's data to
-
# the <tt>default_internal</tt>.
-
#
-
# To do so, simply raise the raise +WrongEncodingError+
-
# as follows:
-
#
-
# raise WrongEncodingError.new(
-
# problematic_string,
-
# expected_encoding
-
# )
-
-
1
eager_autoload do
-
1
autoload :Error
-
1
autoload :Handlers
-
1
autoload :Text
-
1
autoload :Types
-
end
-
-
1
extend Template::Handlers
-
-
1
attr_accessor :locals, :formats, :virtual_path
-
-
1
attr_reader :source, :identifier, :handler, :original_encoding, :updated_at
-
-
# This finalizer is needed (and exactly with a proc inside another proc)
-
# otherwise templates leak in development.
-
1
Finalizer = proc do |method_name, mod|
-
767
proc do
-
398
mod.module_eval do
-
398
remove_possible_method method_name
-
end
-
end
-
end
-
-
1
def initialize(source, identifier, handler, details)
-
893
format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))
-
-
893
@source = source
-
893
@identifier = identifier
-
893
@handler = handler
-
893
@compiled = false
-
893
@original_encoding = nil
-
893
@locals = details[:locals] || []
-
893
@virtual_path = details[:virtual_path]
-
893
@updated_at = details[:updated_at] || Time.now
-
1239
@formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f }
-
893
@compile_mutex = Mutex.new
-
end
-
-
# Returns if the underlying handler supports streaming. If so,
-
# a streaming buffer *may* be passed when it start rendering.
-
1
def supports_streaming?
-
15
handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
-
end
-
-
# Render a template. If the template was not compiled yet, it is done
-
# exactly before rendering.
-
#
-
# This method is instrumented as "!render_template.action_view". Notice that
-
# we use a bang in this instrumentation because you don't want to
-
# consume this in production. This is only slow if it's being listened to.
-
1
def render(view, locals, buffer=nil, &block)
-
1079
ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
-
1079
compile!(view)
-
1075
view.send(method_name, locals, buffer, &block)
-
end
-
rescue Exception => e
-
23
handle_render_error(view, e)
-
end
-
-
1
def mime_type
-
1
message = 'Template#mime_type is deprecated and will be removed in Rails 4.1. Please use type method instead.'
-
1
ActiveSupport::Deprecation.warn message
-
1
@mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first
-
end
-
-
1
def type
-
@type ||= Types[@formats.first] if @formats.first
-
end
-
-
# Receives a view object and return a template similar to self by using @virtual_path.
-
#
-
# This method is useful if you have a template object but it does not contain its source
-
# anymore since it was already compiled. In such cases, all you need to do is to call
-
# refresh passing in the view object.
-
#
-
# Notice this method raises an error if the template to be refreshed does not have a
-
# virtual path set (true just for inline templates).
-
1
def refresh(view)
-
15
raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path
-
14
lookup = view.lookup_context
-
14
pieces = @virtual_path.split("/")
-
14
name = pieces.pop
-
14
partial = !!name.sub!(/^_/, "")
-
14
lookup.disable_cache do
-
14
lookup.find_template(name, [ pieces.join('/') ], partial, @locals)
-
end
-
end
-
-
1
def inspect
-
776
@inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
-
end
-
-
# This method is responsible for properly setting the encoding of the
-
# source. Until this point, we assume that the source is BINARY data.
-
# If no additional information is supplied, we assume the encoding is
-
# the same as <tt>Encoding.default_external</tt>.
-
#
-
# The user can also specify the encoding via a comment on the first
-
# line of the template (# encoding: NAME-OF-ENCODING). This will work
-
# with any template engine, as we process out the encoding comment
-
# before passing the source on to the template engine, leaving a
-
# blank line in its stead.
-
1
def encode!
-
783
return unless source.encoding == Encoding::BINARY
-
-
# Look for # encoding: *. If we find one, we'll encode the
-
# String in that encoding, otherwise, we'll use the
-
# default external encoding.
-
468
if source.sub!(/\A#{ENCODING_FLAG}/, '')
-
2
encoding = magic_encoding = $1
-
else
-
466
encoding = Encoding.default_external
-
end
-
-
# Tag the source with the default external encoding
-
# or the encoding specified in the file
-
468
source.force_encoding(encoding)
-
-
# If the user didn't specify an encoding, and the handler
-
# handles encodings, we simply pass the String as is to
-
# the handler (with the default_external tag)
-
468
if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
-
443
source
-
# Otherwise, if the String is valid in the encoding,
-
# encode immediately to default_internal. This means
-
# that if a handler doesn't handle encodings, it will
-
# always get Strings in the default_internal
-
25
elsif source.valid_encoding?
-
24
source.encode!
-
# Otherwise, since the String is invalid in the encoding
-
# specified, raise an exception
-
else
-
1
raise WrongEncodingError.new(source, encoding)
-
end
-
end
-
-
1
protected
-
-
# Compile a template. This method ensures a template is compiled
-
# just once and removes the source after it is compiled.
-
1
def compile!(view) #:nodoc:
-
1079
return if @compiled
-
-
# Templates can be used concurrently in threaded environments
-
# so compilation and any instance variable modification must
-
# be synchronized
-
771
@compile_mutex.synchronize do
-
# Any thread holding this lock will be compiling the template needed
-
# by the threads waiting. So re-check the @compiled flag to avoid
-
# re-compilation
-
771
return if @compiled
-
-
771
if view.is_a?(ActionView::CompiledTemplates)
-
756
mod = ActionView::CompiledTemplates
-
else
-
15
mod = view.singleton_class
-
end
-
-
771
compile(view, mod)
-
-
# Just discard the source if we have a virtual path. This
-
# means we can get the template back.
-
767
@source = nil if @virtual_path
-
767
@compiled = true
-
end
-
end
-
-
# Among other things, this method is responsible for properly setting
-
# the encoding of the compiled template.
-
#
-
# If the template engine handles encodings, we send the encoded
-
# String to the engine without further processing. This allows
-
# the template engine to support additional mechanisms for
-
# specifying the encoding. For instance, ERB supports <%# encoding: %>
-
#
-
# Otherwise, after we figure out the correct encoding, we then
-
# encode the source into <tt>Encoding.default_internal</tt>.
-
# In general, this means that templates will be UTF-8 inside of Rails,
-
# regardless of the original source encoding.
-
1
def compile(view, mod) #:nodoc:
-
771
encode!
-
770
method_name = self.method_name
-
770
code = @handler.call(self)
-
-
# Make sure that the resulting String to be evalled is in the
-
# encoding of the code
-
767
source = <<-end_src
-
def #{method_name}(local_assigns, output_buffer)
-
_old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
-
ensure
-
@virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
-
end
-
end_src
-
-
# Make sure the source is in the encoding of the returned code
-
767
source.force_encoding(code.encoding)
-
-
# In case we get back a String from a handler that is not in
-
# BINARY or the default_internal, encode it to the default_internal
-
767
source.encode!
-
-
# Now, validate that the source we got back from the template
-
# handler is valid in the default_internal. This is for handlers
-
# that handle encoding but screw up
-
767
unless source.valid_encoding?
-
raise WrongEncodingError.new(@source, Encoding.default_internal)
-
end
-
-
767
begin
-
767
mod.module_eval(source, identifier, 0)
-
767
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
-
rescue Exception => e # errors from template code
-
if logger = (view && view.logger)
-
logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
-
logger.debug "Function body: #{source}"
-
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
-
end
-
-
raise ActionView::Template::Error.new(self, e)
-
end
-
end
-
-
1
def handle_render_error(view, e) #:nodoc:
-
23
if e.is_a?(Template::Error)
-
3
e.sub_template_of(self)
-
3
raise e
-
else
-
20
template = self
-
20
unless template.source
-
12
template = refresh(view)
-
12
template.encode!
-
end
-
20
raise Template::Error.new(template, e)
-
end
-
end
-
-
1
def locals_code #:nodoc:
-
1035
@locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join
-
end
-
-
1
def method_name #:nodoc:
-
1845
@method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_")
-
end
-
-
1
def identifier_method_name #:nodoc:
-
770
inspect.gsub(/[^a-z_]/, '_')
-
end
-
end
-
end
-
1
require "active_support/core_ext/enumerable"
-
-
1
module ActionView
-
# = Action View Errors
-
1
class ActionViewError < StandardError #:nodoc:
-
end
-
-
1
class EncodingError < StandardError #:nodoc:
-
end
-
-
1
class MissingRequestError < StandardError #:nodoc:
-
end
-
-
1
class WrongEncodingError < EncodingError #:nodoc:
-
1
def initialize(string, encoding)
-
4
@string, @encoding = string, encoding
-
end
-
-
1
def message
-
6
@string.force_encoding("BINARY")
-
6
"Your template was not saved as valid #{@encoding}. Please " \
-
"either specify #{@encoding} as the encoding for your template " \
-
"in your text editor, or mark the template with its " \
-
"encoding by inserting the following as the first line " \
-
"of the template:\n\n# encoding: <name of correct encoding>.\n\n" \
-
"The source of your template was:\n\n#{@string}"
-
end
-
end
-
-
1
class MissingTemplate < ActionViewError #:nodoc:
-
1
attr_reader :path
-
-
1
def initialize(paths, path, prefixes, partial, details, *)
-
72
@path = path
-
72
prefixes = Array(prefixes)
-
72
template_type = if partial
-
25
"partial"
-
elsif path =~ /layouts/i
-
'layout'
-
else
-
47
'template'
-
end
-
-
132
searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
-
-
72
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
-
148
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
-
72
super out
-
end
-
end
-
-
1
class Template
-
# The Template::Error exception is raised when the compilation or rendering of the template
-
# fails. This exception then gathers a bunch of intimate details and uses it to report a
-
# precise exception message.
-
1
class Error < ActionViewError #:nodoc:
-
1
SOURCE_CODE_RADIUS = 3
-
-
1
attr_reader :original_exception, :backtrace
-
-
1
def initialize(template, original_exception)
-
27
super(original_exception.message)
-
27
@template, @original_exception = template, original_exception
-
27
@sub_templates = nil
-
27
@backtrace = original_exception.backtrace
-
end
-
-
1
def file_name
-
27
@template.identifier
-
end
-
-
1
def sub_template_message
-
7
if @sub_templates
-
"Trace of template inclusion: " +
-
4
@sub_templates.collect { |template| template.inspect }.join(", ")
-
else
-
5
""
-
end
-
end
-
-
1
def source_extract(indentation = 0)
-
8
return unless num = line_number
-
8
num = num.to_i
-
-
8
source_code = @template.source.split("\n")
-
-
8
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
-
8
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
-
-
8
indent = end_on_line.to_s.size + indentation
-
8
line_counter = start_on_line
-
8
return unless source_code = source_code[start_on_line..end_on_line]
-
-
8
source_code.sum do |line|
-
18
line_counter += 1
-
18
"%#{indent}s: %s\n" % [line_counter, line]
-
end
-
end
-
-
1
def sub_template_of(template_path)
-
3
@sub_templates ||= []
-
3
@sub_templates << template_path
-
end
-
-
1
def line_number
-
@line_number ||=
-
if file_name
-
10
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
-
20
$1 if message =~ regexp || backtrace.find { |line| line =~ regexp }
-
18
end
-
end
-
-
1
def annoted_source_code
-
7
source_extract(4)
-
end
-
-
1
private
-
-
1
def source_location
-
if line_number
-
"on line ##{line_number} of "
-
else
-
'in '
-
end + file_name
-
end
-
end
-
end
-
-
1
TemplateError = Template::Error
-
end
-
1
module ActionView #:nodoc:
-
# = Action View Template Handlers
-
1
class Template
-
1
module Handlers #:nodoc:
-
1
autoload :ERB, 'action_view/template/handlers/erb'
-
1
autoload :Builder, 'action_view/template/handlers/builder'
-
1
autoload :Raw, 'action_view/template/handlers/raw'
-
-
1
def self.extended(base)
-
1
base.register_default_template_handler :erb, ERB.new
-
1
base.register_template_handler :builder, Builder.new
-
1
base.register_template_handler :raw, Raw.new
-
1
base.register_template_handler :ruby, :source.to_proc
-
end
-
-
1
@@template_handlers = {}
-
1
@@default_template_handlers = nil
-
-
1
def self.extensions
-
1871
@@template_extensions ||= @@template_handlers.keys
-
end
-
-
# Register an object that knows how to handle template files with the given
-
# extensions. This can be used to implement new template types.
-
# The handler must respond to `:call`, which will be passed the template
-
# and should return the rendered template as a String.
-
1
def register_template_handler(*extensions, handler)
-
17
raise(ArgumentError, "Extension is required") if extensions.empty?
-
15
extensions.each do |extension|
-
17
@@template_handlers[extension.to_sym] = handler
-
end
-
15
@@template_extensions = nil
-
end
-
-
1
def template_handler_extensions
-
@@template_handlers.keys.map {|key| key.to_s }.sort
-
end
-
-
1
def registered_template_handler(extension)
-
859
extension && @@template_handlers[extension.to_sym]
-
end
-
-
1
def register_default_template_handler(extension, klass)
-
1
register_template_handler(extension, klass)
-
1
@@default_template_handlers = klass
-
end
-
-
1
def handler_for_extension(extension)
-
859
registered_template_handler(extension) || @@default_template_handlers
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Template::Handlers
-
1
class Builder
-
# Default format used by Builder.
-
1
class_attribute :default_format
-
1
self.default_format = :xml
-
-
1
def call(template)
-
39
require_engine
-
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
-
"self.output_buffer = xml.target!;" +
-
template.source +
-
39
";xml.target!;"
-
end
-
-
1
protected
-
-
1
def require_engine
-
@required ||= begin
-
1
require "builder"
-
1
true
-
39
end
-
end
-
end
-
end
-
end
-
1
require 'action_dispatch/http/mime_type'
-
1
require 'erubis'
-
-
1
module ActionView
-
1
class Template
-
1
module Handlers
-
1
class Erubis < ::Erubis::Eruby
-
1
def add_preamble(src)
-
714
src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
-
end
-
-
1
def add_text(src, text)
-
1691
return if text.empty?
-
1358
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
-
end
-
-
1
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
-
-
1
def add_expr_literal(src, code)
-
882
if code =~ BLOCK_EXPR
-
35
src << '@output_buffer.append= ' << code
-
else
-
847
src << '@output_buffer.append= (' << code << ');'
-
end
-
end
-
-
1
def add_expr_escaped(src, code)
-
1
if code =~ BLOCK_EXPR
-
src << "@output_buffer.safe_append= " << code
-
else
-
1
src << "@output_buffer.safe_concat((" << code << ").to_s);"
-
end
-
end
-
-
1
def add_postamble(src)
-
714
src << '@output_buffer.to_s'
-
end
-
end
-
-
1
class ERB
-
# Specify trim mode for the ERB compiler. Defaults to '-'.
-
# See ERB documentation for suitable values.
-
1
class_attribute :erb_trim_mode
-
1
self.erb_trim_mode = '-'
-
-
# Default implementation used.
-
1
class_attribute :erb_implementation
-
1
self.erb_implementation = Erubis
-
-
1
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
-
-
1
def self.call(template)
-
14
new.call(template)
-
end
-
-
1
def supports_streaming?
-
15
true
-
end
-
-
1
def handles_encoding?
-
443
true
-
end
-
-
1
def call(template)
-
# First, convert to BINARY, so in case the encoding is
-
# wrong, we can still find an encoding tag
-
# (<%# encoding %>) inside the String using a regular
-
# expression
-
711
template_source = template.source.dup.force_encoding("BINARY")
-
-
711
erb = template_source.gsub(ENCODING_TAG, '')
-
711
encoding = $2
-
-
711
erb.force_encoding valid_encoding(template.source.dup, encoding)
-
-
# Always make sure we return a String in the default_internal
-
708
erb.encode!
-
-
708
self.class.erb_implementation.new(
-
erb,
-
708
:trim => (self.class.erb_trim_mode == "-")
-
).src
-
end
-
-
1
private
-
-
1
def valid_encoding(string, encoding)
-
# If a magic encoding comment was found, tag the
-
# String with this encoding. This is for a case
-
# where the original String was assumed to be,
-
# for instance, UTF-8, but a magic comment
-
# proved otherwise
-
711
string.force_encoding(encoding) if encoding
-
-
# If the String is valid, return the encoding we found
-
711
return string.encoding if string.valid_encoding?
-
-
# Otherwise, raise an exception
-
3
raise WrongEncodingError.new(string, string.encoding)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Template::Handlers
-
1
class Raw
-
1
def call(template)
-
5
escaped = template.source.gsub(':', '\:')
-
-
5
'%q:' + escaped + ':;'
-
end
-
end
-
end
-
end
-
1
require "pathname"
-
1
require "active_support/core_ext/class"
-
1
require "active_support/core_ext/class/attribute_accessors"
-
1
require "action_view/template"
-
1
require "thread"
-
1
require "mutex_m"
-
-
1
module ActionView
-
# = Action View Resolver
-
1
class Resolver
-
# Keeps all information about view path and builds virtual path.
-
1
class Path
-
1
attr_reader :name, :prefix, :partial, :virtual
-
1
alias_method :partial?, :partial
-
-
1
def self.build(name, prefix, partial)
-
795
virtual = ""
-
795
virtual << "#{prefix}/" unless prefix.empty?
-
795
virtual << (partial ? "_#{name}" : name)
-
795
new name, prefix, partial, virtual
-
end
-
-
1
def initialize(name, prefix, partial, virtual)
-
795
@name = name
-
795
@prefix = prefix
-
795
@partial = partial
-
795
@virtual = virtual
-
end
-
-
1
def to_str
-
625
@virtual
-
end
-
1
alias :to_s :to_str
-
end
-
-
# Threadsafe template cache
-
1
class Cache #:nodoc:
-
1
class CacheEntry
-
1
include Mutex_m
-
-
1
attr_accessor :templates
-
end
-
-
1
def initialize
-
535
@data = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
-
2178
h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
-
294
@mutex = Mutex.new
-
end
-
-
# Cache the templates returned by the block
-
1
def cache(key, name, prefix, partial, locals)
-
1264
cache_entry = nil
-
-
# first obtain a lock on the main data structure to create the cache entry
-
1264
@mutex.synchronize do
-
1264
cache_entry = @data[key][name][prefix][partial][locals] ||= CacheEntry.new
-
end
-
-
# then to avoid a long lasting global lock, obtain a more granular lock
-
# on the CacheEntry itself
-
1264
cache_entry.synchronize do
-
1264
if Resolver.caching?
-
1257
cache_entry.templates ||= yield
-
else
-
7
fresh_templates = yield
-
-
7
if templates_have_changed?(cache_entry.templates, fresh_templates)
-
5
cache_entry.templates = fresh_templates
-
else
-
2
cache_entry.templates ||= []
-
end
-
end
-
end
-
end
-
-
1
def clear
-
1
@mutex.synchronize do
-
1
@data.clear
-
end
-
end
-
-
1
private
-
-
1
def templates_have_changed?(cached_templates, fresh_templates)
-
# if either the old or new template list is empty, we don't need to (and can't)
-
# compare modification times, and instead just check whether the lists are different
-
7
if cached_templates.blank? || fresh_templates.blank?
-
5
return fresh_templates.blank? != cached_templates.blank?
-
end
-
-
2
cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
-
-
# if a template has changed, it will be now be newer than all the cached templates
-
4
fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
-
end
-
end
-
-
1
cattr_accessor :caching
-
1
self.caching = true
-
-
1
class << self
-
1
alias :caching? :caching
-
end
-
-
1
def initialize
-
294
@cache = Cache.new
-
end
-
-
1
def clear_cache
-
1
@cache.clear
-
end
-
-
# Normalizes the arguments and passes it on to find_template.
-
1
def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
-
1284
cached(key, [name, prefix, partial], details, locals) do
-
795
find_templates(name, prefix, partial, details)
-
end
-
end
-
-
1
private
-
-
1
delegate :caching?, :to => "self.class"
-
-
# This is what child classes implement. No defaults are needed
-
# because Resolver guarantees that the arguments are present and
-
# normalized.
-
1
def find_templates(name, prefix, partial, details)
-
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method"
-
end
-
-
# Helpers that builds a path. Useful for building virtual paths.
-
1
def build_path(name, prefix, partial)
-
Path.build(name, prefix, partial)
-
end
-
-
# Handles templates caching. If a key is given and caching is on
-
# always check the cache before hitting the resolver. Otherwise,
-
# it always hits the resolver but if the key is present, check if the
-
# resolver is fresher before returning it.
-
1
def cached(key, path_info, details, locals) #:nodoc:
-
1284
name, prefix, partial = path_info
-
1673
locals = locals.map { |x| x.to_s }.sort!
-
-
1284
if key
-
1264
@cache.cache(key, name, prefix, partial, locals) do
-
775
decorate(yield, path_info, details, locals)
-
end
-
else
-
20
decorate(yield, path_info, details, locals)
-
end
-
end
-
-
# Ensures all the resolver information is set in the template.
-
1
def decorate(templates, path_info, details, locals) #:nodoc:
-
795
cached = nil
-
795
templates.each do |t|
-
649
t.locals = locals
-
649
t.formats = details[:formats] || [:html] if t.formats.empty?
-
649
t.virtual_path ||= (cached ||= build_path(*path_info))
-
end
-
end
-
end
-
-
# An abstract class that implements a Resolver with path semantics.
-
1
class PathResolver < Resolver #:nodoc:
-
1
EXTENSIONS = [:locale, :formats, :handlers]
-
1
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}"
-
-
1
def initialize(pattern=nil)
-
294
@pattern = pattern || DEFAULT_PATTERN
-
294
super()
-
end
-
-
1
private
-
-
1
def find_templates(name, prefix, partial, details)
-
795
path = Path.build(name, prefix, partial)
-
795
query(path, details, details[:formats])
-
end
-
-
1
def query(path, details, formats)
-
631
query = build_query(path, details)
-
-
# deals with case-insensitive file systems.
-
1116
sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
-
-
631
template_paths = Dir[query].reject { |filename|
-
File.directory?(filename) ||
-
550
!sanitizer[File.dirname(filename)].include?(filename)
-
}
-
-
631
template_paths.map { |template|
-
546
handler, format = extract_handler_and_format(template, formats)
-
546
contents = File.binread template
-
-
546
Template.new(contents, File.expand_path(template), handler,
-
:virtual_path => path.virtual,
-
:format => format,
-
:updated_at => mtime(template))
-
}
-
end
-
-
# Helper for building query glob string based on resolver's pattern.
-
1
def build_query(path, details)
-
171
query = @pattern.dup
-
-
171
prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
-
171
query.gsub!(/\:prefix(\/)?/, prefix)
-
-
171
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
-
171
query.gsub!(/\:action/, partial)
-
-
171
details.each do |ext, variants|
-
513
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
-
end
-
-
171
File.expand_path(query, @path)
-
end
-
-
1
def escape_entry(entry)
-
789
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
-
end
-
-
# Returns the file mtime from the filesystem.
-
1
def mtime(p)
-
546
File.mtime(p)
-
end
-
-
# Extract handler and formats from path. If a format cannot be a found neither
-
# from the path, or the handler, we should return the array of formats given
-
# to the resolver.
-
1
def extract_handler_and_format(path, default_formats)
-
649
pieces = File.basename(path).split(".")
-
649
pieces.shift
-
-
649
extension = pieces.pop
-
649
unless extension
-
message = "The file #{path} did not specify a template handler. The default is currently ERB, " \
-
"but will change to RAW in the future."
-
ActiveSupport::Deprecation.warn message
-
end
-
-
649
handler = Template.handler_for_extension(extension)
-
649
format = pieces.last && Template::Types[pieces.last]
-
649
[handler, format]
-
end
-
end
-
-
# A resolver that loads files from the filesystem. It allows setting your own
-
# resolving pattern. Such pattern can be a glob string supported by some variables.
-
#
-
# ==== Examples
-
#
-
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
-
# looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
-
#
-
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
-
#
-
# This one allows you to keep files with different formats in seperated subdirectories,
-
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
-
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
-
#
-
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
-
#
-
# If you don't specify a pattern then the default will be used.
-
#
-
# In order to use any of the customized resolvers above in a Rails application, you just need
-
# to configure ActionController::Base.view_paths in an initializer, for example:
-
#
-
# ActionController::Base.view_paths = FileSystemResolver.new(
-
# Rails.root.join("app/views"),
-
# ":prefix{/:locale}/:action{.:formats,}{.:handlers,}"
-
# )
-
#
-
# ==== Pattern format and variables
-
#
-
# Pattern has to be a valid glob string, and it allows you to use the
-
# following variables:
-
#
-
# * <tt>:prefix</tt> - usually the controller path
-
# * <tt>:action</tt> - name of the action
-
# * <tt>:locale</tt> - possible locale versions
-
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
-
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
-
#
-
1
class FileSystemResolver < PathResolver
-
1
def initialize(path, pattern=nil)
-
261
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
-
261
super(pattern)
-
261
@path = File.expand_path(path)
-
end
-
-
1
def to_s
-
579
@path.to_s
-
end
-
1
alias :to_path :to_s
-
-
1
def eql?(resolver)
-
447
self.class.equal?(resolver.class) && to_path == resolver.to_path
-
end
-
1
alias :== :eql?
-
end
-
-
# An Optimized resolver for Rails' most common case.
-
1
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
-
1
def build_query(path, details)
-
1840
exts = EXTENSIONS.map { |ext| details[ext] }
-
460
query = escape_entry(File.join(@path, path))
-
-
query + exts.map { |ext|
-
9814
"{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}"
-
460
}.join
-
end
-
end
-
-
# The same as FileSystemResolver but does not allow templates to store
-
# a virtual path since it is invalid for such resolvers.
-
1
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
-
1
def self.instances
-
1
[new(""), new("/")]
-
end
-
-
1
def decorate(*)
-
62
super.each { |t| t.virtual_path = nil }
-
end
-
end
-
end
-
1
module ActionView #:nodoc:
-
# = Action View Text Template
-
1
class Template
-
1
class Text #:nodoc:
-
1
attr_accessor :type
-
-
1
def initialize(string, type = nil)
-
554
@string = string.to_s
-
554
@type = Types[type] || type if type
-
554
@type ||= Types[:text]
-
end
-
-
1
def identifier
-
551
'text template'
-
end
-
-
1
def inspect
-
'text template'
-
end
-
-
1
def to_str
-
551
@string
-
end
-
-
1
def render(*args)
-
551
to_str
-
end
-
-
1
def formats
-
1022
[@type.to_sym]
-
end
-
end
-
end
-
end
-
1
require 'set'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/object/blank'
-
-
1
module ActionView
-
1
class Template
-
1
class Types
-
1
class Type
-
1
cattr_accessor :types
-
1
self.types = Set.new
-
-
1
def self.register(*t)
-
7
types.merge(t.map { |type| type.to_s })
-
end
-
-
1
register :html, :text, :js, :css, :xml, :json
-
-
1
def self.[](type)
-
return type if type.is_a?(self)
-
-
if type.is_a?(Symbol) || types.member?(type.to_s)
-
new(type)
-
end
-
end
-
-
1
attr_reader :symbol
-
-
1
def initialize(symbol)
-
@symbol = symbol.to_sym
-
end
-
-
1
delegate :to_s, :to_sym, :to => :symbol
-
1
alias to_str to_s
-
-
1
def ref
-
to_sym || to_s
-
end
-
-
1
def ==(type)
-
return false if type.blank?
-
symbol.to_sym == type.to_sym
-
end
-
end
-
-
1
cattr_accessor :type_klass
-
-
1
def self.delegate_to(klass)
-
2
self.type_klass = klass
-
end
-
-
1
delegate_to Type
-
-
1
def self.[](type)
-
873
type_klass[type]
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'action_controller'
-
1
require 'action_controller/test_case'
-
1
require 'action_view'
-
-
1
module ActionView
-
# = Action View Test Case
-
1
class TestCase < ActiveSupport::TestCase
-
1
class TestController < ActionController::Base
-
1
include ActionDispatch::TestProcess
-
-
1
attr_accessor :request, :response, :params
-
-
1
class << self
-
1
attr_writer :controller_path
-
end
-
-
1
def controller_path=(path)
-
8
self.class.controller_path=(path)
-
end
-
-
1
def initialize
-
963
super
-
963
self.class.controller_path = ""
-
963
@request = ActionController::TestRequest.new
-
963
@response = ActionController::TestResponse.new
-
-
963
@request.env.delete('PATH_INFO')
-
963
@params = {}
-
end
-
end
-
-
# Use AV::TestCase for the base class for helpers and views
-
1
register_spec_type(/(Helper|View)( ?Test)?\z/i, self)
-
-
1
module Behavior
-
1
extend ActiveSupport::Concern
-
-
1
include ActionDispatch::Assertions, ActionDispatch::TestProcess
-
1
include ActionController::TemplateAssertions
-
1
include ActionView::Context
-
-
1
include ActionDispatch::Routing::PolymorphicRoutes
-
-
1
include AbstractController::Helpers
-
1
include ActionView::Helpers
-
1
include ActionView::RecordIdentifier
-
1
include ActionView::RoutingUrlFor
-
-
1
include ActiveSupport::Testing::ConstantLookup
-
-
1
delegate :lookup_context, :to => :controller
-
1
attr_accessor :controller, :output_buffer, :rendered
-
-
1
module ClassMethods
-
1
def tests(helper_class)
-
22
case helper_class
-
when String, Symbol
-
2
self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
-
when Module
-
20
self.helper_class = helper_class
-
end
-
end
-
-
1
def determine_default_helper_class(name)
-
95
determine_constant_from_test_name(name) do |constant|
-
21
Module === constant && !(Class === constant)
-
end
-
end
-
-
1
def helper_method(*methods)
-
# Almost a duplicate from ActionController::Helpers
-
2
methods.flatten.each do |method|
-
2
_helpers.module_eval <<-end_eval
-
def #{method}(*args, &block) # def current_user(*args, &block)
-
_test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block)
-
end # end
-
end_eval
-
end
-
end
-
-
1
attr_writer :helper_class
-
-
1
def helper_class
-
1861
@helper_class ||= determine_default_helper_class(name)
-
end
-
-
1
def new(*)
-
961
include_helper_modules!
-
961
super
-
end
-
-
1
private
-
-
1
def include_helper_modules!
-
961
helper(helper_class) if helper_class
-
961
include _helpers
-
end
-
-
end
-
-
1
def setup_with_controller
-
961
@controller = ActionView::TestCase::TestController.new
-
961
@request = @controller.request
-
961
@output_buffer = ActiveSupport::SafeBuffer.new
-
961
@rendered = ''
-
-
961
make_test_case_available_to_view!
-
961
say_no_to_protect_against_forgery!
-
end
-
-
1
def config
-
795
@controller.config if @controller.respond_to?(:config)
-
end
-
-
1
def render(options = {}, local_assigns = {}, &block)
-
18
view.assign(view_assigns)
-
18
@rendered << output = view.render(options, local_assigns, &block)
-
18
output
-
end
-
-
1
def rendered_views
-
21
@_rendered_views ||= RenderedViewsCollection.new
-
end
-
-
1
class RenderedViewsCollection
-
1
def initialize
-
21
@rendered_views ||= {}
-
end
-
-
1
def add(view, locals)
-
18
@rendered_views[view] ||= []
-
18
@rendered_views[view] << locals
-
end
-
-
1
def locals_for(view)
-
8
@rendered_views[view]
-
end
-
-
1
def view_rendered?(view, expected_locals)
-
4
locals_for(view).any? do |actual_locals|
-
10
expected_locals.all? {|key, value| value == actual_locals[key] }
-
end
-
end
-
end
-
-
1
included do
-
1
setup :setup_with_controller
-
end
-
-
1
private
-
-
# Support the selector assertions
-
#
-
# Need to experiment if this priority is the best one: rendered => output_buffer
-
1
def response_from_page
-
83
HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root
-
end
-
-
1
def say_no_to_protect_against_forgery!
-
961
_helpers.module_eval do
-
961
remove_possible_method :protect_against_forgery?
-
961
def protect_against_forgery?
-
2
false
-
end
-
end
-
end
-
-
1
def make_test_case_available_to_view!
-
961
test_case_instance = self
-
961
_helpers.module_eval do
-
961
unless private_method_defined?(:_test_case)
-
46
define_method(:_test_case) { test_case_instance }
-
44
private :_test_case
-
end
-
end
-
end
-
-
1
module Locals
-
1
attr_accessor :rendered_views
-
-
1
def render(options = {}, local_assigns = {})
-
26
case options
-
when Hash
-
24
if block_given?
-
1
rendered_views.add options[:layout], options[:locals]
-
elsif options.key?(:partial)
-
15
rendered_views.add options[:partial], options[:locals]
-
end
-
else
-
2
rendered_views.add options, local_assigns
-
end
-
-
26
super
-
end
-
end
-
-
# The instance of ActionView::Base that is used by +render+.
-
1
def view
-
@view ||= begin
-
21
view = @controller.view_context
-
21
view.singleton_class.send :include, _helpers
-
21
view.extend(Locals)
-
21
view.rendered_views = self.rendered_views
-
21
view.output_buffer = self.output_buffer
-
21
view
-
46
end
-
end
-
-
1
alias_method :_view, :view
-
-
1
INTERNAL_IVARS = [
-
:@__name__,
-
:@__io__,
-
:@_assertion_wrapped,
-
:@_assertions,
-
:@_result,
-
:@_routes,
-
:@controller,
-
:@_layouts,
-
:@_rendered_views,
-
:@method_name,
-
:@output_buffer,
-
:@_partials,
-
:@passed,
-
:@rendered,
-
:@request,
-
:@routes,
-
:@tagged_logger,
-
:@_templates,
-
:@options,
-
:@test_passed,
-
:@view,
-
:@view_context_class
-
]
-
-
1
def _user_defined_ivars
-
41
instance_variables - INTERNAL_IVARS
-
end
-
-
# Returns a Hash of instance variables and their values, as defined by
-
# the user in the test case, which are then assigned to the view being
-
# rendered. This is generally intended for internal use and extension
-
# frameworks.
-
1
def view_assigns
-
41
Hash[_user_defined_ivars.map do |ivar|
-
5
[ivar[1..-1].to_sym, instance_variable_get(ivar)]
-
end]
-
end
-
-
1
def _routes
-
2
@controller._routes if @controller.respond_to?(:_routes)
-
end
-
-
1
def method_missing(selector, *args)
-
if @controller.respond_to?(:_routes) &&
-
( @controller._routes.named_routes.helpers.include?(selector) ||
-
6
@controller._routes.mounted_helpers.method_defined?(selector) )
-
5
@controller.__send__(selector, *args)
-
else
-
1
super
-
end
-
end
-
end
-
-
1
include Behavior
-
end
-
end
-
1
require 'action_view/template/resolver'
-
-
1
module ActionView #:nodoc:
-
# Use FixtureResolver in your tests to simulate the presence of files on the
-
# file system. This is used internally by Rails' own test suite, and is
-
# useful for testing extensions that have no way of knowing what the file
-
# system will look like at runtime.
-
1
class FixtureResolver < PathResolver
-
1
attr_reader :hash
-
-
1
def initialize(hash = {}, pattern=nil)
-
32
super(pattern)
-
32
@hash = hash
-
end
-
-
1
def to_s
-
25
@hash.keys.join(', ')
-
end
-
-
1
private
-
-
1
def query(path, exts, formats)
-
163
query = ""
-
163
EXTENSIONS.each do |ext|
-
2870
query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
-
end
-
163
query = /^(#{Regexp.escape(path)})#{query}$/
-
-
163
templates = []
-
163
@hash.each do |_path, array|
-
747
source, updated_at = array
-
747
next unless _path =~ query
-
102
handler, format = extract_handler_and_format(_path, formats)
-
templates << Template.new(source, _path, handler,
-
102
:virtual_path => path.virtual, :format => format, :updated_at => updated_at)
-
end
-
-
265
templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
-
end
-
end
-
-
1
class NullResolver < PathResolver
-
1
def query(path, exts, formats)
-
1
handler, format = extract_handler_and_format(path, formats)
-
1
[ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)]
-
end
-
end
-
-
end
-
-
1
$LOAD_PATH << "#{File.dirname(__FILE__)}/html-scanner"
-
-
1
module HTML
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :CDATA, 'html/node'
-
1
autoload :Document, 'html/document'
-
1
autoload :FullSanitizer, 'html/sanitizer'
-
1
autoload :LinkSanitizer, 'html/sanitizer'
-
1
autoload :Node, 'html/node'
-
1
autoload :Sanitizer, 'html/sanitizer'
-
1
autoload :Selector, 'html/selector'
-
1
autoload :Tag, 'html/node'
-
1
autoload :Text, 'html/node'
-
1
autoload :Tokenizer, 'html/tokenizer'
-
1
autoload :Version, 'html/version'
-
1
autoload :WhiteListSanitizer, 'html/sanitizer'
-
end
-
end
-
1
require 'html/tokenizer'
-
1
require 'html/node'
-
1
require 'html/selector'
-
1
require 'html/sanitizer'
-
-
1
module HTML #:nodoc:
-
# A top-level HTML document. You give it a body of text, and it will parse that
-
# text into a tree of nodes.
-
1
class Document #:nodoc:
-
-
# The root of the parsed document.
-
1
attr_reader :root
-
-
# Create a new Document from the given text.
-
1
def initialize(text, strict=false, xml=false)
-
2324
tokenizer = Tokenizer.new(text)
-
2324
@root = Node.new(nil)
-
2324
node_stack = [ @root ]
-
2324
while token = tokenizer.next
-
96516
node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict)
-
-
96515
node_stack.last.children << node unless node.tag? && node.closing == :close
-
96515
if node.tag?
-
50386
if node_stack.length > 1 && node.closing == :close
-
24171
if node_stack.last.name == node.name
-
24171
if node_stack.last.children.empty?
-
260
node_stack.last.children << Text.new(node_stack.last, node.line, node.position, "")
-
end
-
24171
node_stack.pop
-
else
-
open_start = node_stack.last.position - 20
-
open_start = 0 if open_start < 0
-
close_start = node.position - 20
-
close_start = 0 if close_start < 0
-
msg = <<EOF.strip
-
ignoring attempt to close #{node_stack.last.name} with #{node.name}
-
opened at byte #{node_stack.last.position}, line #{node_stack.last.line}
-
closed at byte #{node.position}, line #{node.line}
-
attributes at open: #{node_stack.last.attributes.inspect}
-
text around open: #{text[open_start,40].inspect}
-
text around close: #{text[close_start,40].inspect}
-
EOF
-
strict ? raise(msg) : warn(msg)
-
end
-
elsif !node.childless?(xml) && node.closing != :close
-
24204
node_stack.push node
-
end
-
end
-
end
-
end
-
-
# Search the tree for (and return) the first node that matches the given
-
# conditions. The conditions are interpreted differently for different node
-
# types, see HTML::Text#find and HTML::Tag#find.
-
1
def find(conditions)
-
53
@root.find(conditions)
-
end
-
-
# Search the tree for (and return) all nodes that match the given
-
# conditions. The conditions are interpreted differently for different node
-
# types, see HTML::Text#find and HTML::Tag#find.
-
1
def find_all(conditions)
-
1
@root.find_all(conditions)
-
end
-
-
end
-
-
end
-
1
require 'strscan'
-
-
1
module HTML #:nodoc:
-
-
1
class Conditions < Hash #:nodoc:
-
1
def initialize(hash)
-
396
super()
-
396
hash = { :content => hash } unless Hash === hash
-
396
hash = keys_to_symbols(hash)
-
396
hash.each do |k,v|
-
449
case k
-
when :tag, :content then
-
# keys are valid, and require no further processing
-
when :attributes then
-
66
hash[k] = keys_to_strings(v)
-
when :parent, :child, :ancestor, :descendant, :sibling, :before,
-
:after
-
139
hash[k] = Conditions.new(v)
-
when :children
-
19
hash[k] = v = keys_to_symbols(v)
-
19
v.each do |key,value|
-
26
case key
-
when :count, :greater_than, :less_than
-
# keys are valid, and require no further processing
-
when :only
-
7
v[key] = Conditions.new(value)
-
else
-
raise "illegal key #{key.inspect} => #{value.inspect}"
-
end
-
end
-
else
-
raise "illegal key #{k.inspect} => #{v.inspect}"
-
end
-
end
-
396
update hash
-
end
-
-
1
private
-
-
1
def keys_to_strings(hash)
-
140
Hash[hash.keys.map {|k| [k.to_s, hash[k]]}]
-
end
-
-
1
def keys_to_symbols(hash)
-
415
Hash[hash.keys.map do |k|
-
475
raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym)
-
475
[k.to_sym, hash[k]]
-
end]
-
end
-
end
-
-
# The base class of all nodes, textual and otherwise, in an HTML document.
-
1
class Node #:nodoc:
-
# The array of children of this node. Not all nodes have children.
-
1
attr_reader :children
-
-
# The parent node of this node. All nodes have a parent, except for the
-
# root node.
-
1
attr_reader :parent
-
-
# The line number of the input where this node was begun
-
1
attr_reader :line
-
-
# The byte position in the input where this node was begun
-
1
attr_reader :position
-
-
# Create a new node as a child of the given parent.
-
1
def initialize(parent, line=0, pos=0)
-
100027
@parent = parent
-
100027
@children = []
-
100027
@line, @position = line, pos
-
end
-
-
# Return a textual representation of the node.
-
1
def to_s
-
5
@children.join()
-
end
-
-
# Return false (subclasses must override this to provide specific matching
-
# behavior.) +conditions+ may be of any type.
-
1
def match(conditions)
-
15
false
-
end
-
-
# Search the children of this node for the first node for which #find
-
# returns non +nil+. Returns the result of the #find call that succeeded.
-
1
def find(conditions)
-
327
conditions = validate_conditions(conditions)
-
327
@children.each do |child|
-
710
node = child.find(conditions)
-
710
return node if node
-
end
-
nil
-
end
-
-
# Search for all nodes that match the given conditions, and return them
-
# as an array.
-
1
def find_all(conditions)
-
20
conditions = validate_conditions(conditions)
-
-
20
matches = []
-
20
matches << self if match(conditions)
-
20
@children.each do |child|
-
19
matches.concat child.find_all(conditions)
-
end
-
20
matches
-
end
-
-
# Returns +false+. Subclasses may override this if they define a kind of
-
# tag.
-
1
def tag?
-
93997
false
-
end
-
-
1
def validate_conditions(conditions)
-
1649
Conditions === conditions ? conditions : Conditions.new(conditions)
-
end
-
-
1
def ==(node)
-
36501
return false unless self.class == node.class && children.size == node.children.size
-
-
36298
equivalent = true
-
-
36298
children.size.times do |i|
-
35202
equivalent &&= children[i] == node.children[i]
-
end
-
-
36298
equivalent
-
end
-
-
1
class <<self
-
1
def parse(parent, line, pos, content, strict=true)
-
97377
if content !~ /^<\S/
-
46504
Text.new(parent, line, pos, content)
-
else
-
50873
scanner = StringScanner.new(content)
-
-
50873
unless scanner.skip(/</)
-
if strict
-
raise "expected <"
-
else
-
return Text.new(parent, line, pos, content)
-
end
-
end
-
-
50873
if scanner.skip(/!\[CDATA\[/)
-
16
unless scanner.skip_until(/\]\]>/)
-
4
if strict
-
1
raise "expected ]]> (got #{scanner.rest.inspect} for #{content})"
-
else
-
3
scanner.skip_until(/\Z/)
-
end
-
end
-
-
15
return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
-
end
-
-
50857
closing = ( scanner.scan(/\//) ? :close : nil )
-
50857
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
-
50851
name.downcase!
-
-
50851
unless closing
-
26488
scanner.skip(/\s*/)
-
26488
attributes = {}
-
26488
while attr = scanner.scan(/[-\w:]+/)
-
34152
value = true
-
34152
if scanner.scan(/\s*=\s*/)
-
34144
if delim = scanner.scan(/['"]/)
-
34078
value = ""
-
34078
while text = scanner.scan(/[^#{delim}\\]+|./)
-
67823
case text
-
when "\\" then
-
6
value << text
-
6
break if scanner.eos?
-
5
value << scanner.getch
-
when delim
-
34075
break
-
33742
else value << text
-
end
-
end
-
else
-
66
value = scanner.scan(/[^\s>\/]+/)
-
end
-
end
-
34152
attributes[attr.downcase] = value
-
34152
scanner.skip(/\s*/)
-
end
-
-
26488
closing = ( scanner.scan(/\//) ? :self : nil )
-
end
-
-
50851
unless scanner.scan(/\s*>/)
-
31
if strict
-
2
raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})"
-
else
-
# throw away all text until we find what we're looking for
-
29
scanner.skip_until(/>/) or scanner.terminate
-
end
-
end
-
-
50849
Tag.new(parent, line, pos, name, attributes, closing)
-
end
-
end
-
end
-
end
-
-
# A node that represents text, rather than markup.
-
1
class Text < Node #:nodoc:
-
-
1
attr_reader :content
-
-
# Creates a new text node as a child of the given parent, with the given
-
# content.
-
1
def initialize(parent, line, pos, content)
-
46796
super(parent, line, pos)
-
46796
@content = content
-
end
-
-
# Returns the content of this node.
-
1
def to_s
-
413
@content
-
end
-
-
# Returns +self+ if this node meets the given conditions. Text nodes support
-
# conditions of the following kinds:
-
#
-
# * if +conditions+ is a string, it must be a substring of the node's
-
# content
-
# * if +conditions+ is a regular expression, it must match the node's
-
# content
-
# * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that
-
# is either a string or a regexp, and which is interpreted as described
-
# above.
-
1
def find(conditions)
-
410
match(conditions) && self
-
end
-
-
# Returns non-+nil+ if this node meets the given conditions, or +nil+
-
# otherwise. See the discussion of #find for the valid conditions.
-
1
def match(conditions)
-
833
case conditions
-
when String
-
22
@content == conditions
-
when Regexp
-
94
@content =~ conditions
-
when Hash
-
666
conditions = validate_conditions(conditions)
-
-
# Text nodes only have :content, :parent, :ancestor
-
666
unless (conditions.keys - [:content, :parent, :ancestor]).empty?
-
591
return false
-
end
-
-
75
match(conditions[:content])
-
else
-
nil
-
end
-
end
-
-
1
def ==(node)
-
22819
return false unless super
-
22650
content == node.content
-
end
-
end
-
-
# A CDATA node is simply a text node with a specialized way of displaying
-
# itself.
-
1
class CDATA < Text #:nodoc:
-
1
def to_s
-
3
"<![CDATA[#{super}]]>"
-
end
-
end
-
-
# A Tag is any node that represents markup. It may be an opening tag, a
-
# closing tag, or a self-closing tag. It has a name, and may have a hash of
-
# attributes.
-
1
class Tag < Node #:nodoc:
-
-
# Either +nil+, <tt>:close</tt>, or <tt>:self</tt>
-
1
attr_reader :closing
-
-
# Either +nil+, or a hash of attributes for this node.
-
1
attr_reader :attributes
-
-
# The name of this tag.
-
1
attr_reader :name
-
-
# Create a new node as a child of the given parent, using the given content
-
# to describe the node. It will be parsed and the node name, attributes and
-
# closing status extracted.
-
1
def initialize(parent, line, pos, name, attributes, closing)
-
50849
super(parent, line, pos)
-
50849
@name = name
-
50849
@attributes = attributes
-
50849
@closing = closing
-
end
-
-
# A convenience for obtaining an attribute of the node. Returns +nil+ if
-
# the node has no attributes.
-
1
def [](attr)
-
152
@attributes ? @attributes[attr] : nil
-
end
-
-
# Returns non-+nil+ if this tag can contain child nodes.
-
1
def childless?(xml = false)
-
26275
return false if xml && @closing.nil?
-
!@closing.nil? ||
-
@name =~ /^(img|br|hr|link|meta|area|base|basefont|
-
26032
col|frame|input|isindex|param)$/ox
-
end
-
-
# Returns a textual representation of the node
-
1
def to_s
-
209
if @closing == :close
-
78
"</#{@name}>"
-
else
-
131
s = "<#{@name}"
-
131
@attributes.each do |k,v|
-
68
s << " #{k}"
-
68
s << "=\"#{v}\"" if String === v
-
end
-
131
s << " /" if @closing == :self
-
131
s << ">"
-
163
@children.each { |child| s << child.to_s }
-
131
s << "</#{@name}>" if @closing != :self && !@children.empty?
-
131
s
-
end
-
end
-
-
# If either the node or any of its children meet the given conditions, the
-
# matching node is returned. Otherwise, +nil+ is returned. (See the
-
# description of the valid conditions in the +match+ method.)
-
1
def find(conditions)
-
306
match(conditions) && self || super
-
end
-
-
# Returns +true+, indicating that this node represents an HTML tag.
-
1
def tag?
-
102914
true
-
end
-
-
# Returns +true+ if the node meets any of the given conditions. The
-
# +conditions+ parameter must be a hash of any of the following keys
-
# (all are optional):
-
#
-
# * <tt>:tag</tt>: the node name must match the corresponding value
-
# * <tt>:attributes</tt>: a hash. The node's values must match the
-
# corresponding values in the hash.
-
# * <tt>:parent</tt>: a hash. The node's parent must match the
-
# corresponding hash.
-
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
-
# must meet the criteria described by the hash.
-
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
-
# meet the criteria described by the hash.
-
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
-
# must meet the criteria described by the hash.
-
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
-
# meet the criteria described by the hash.
-
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
-
# the criteria described by the hash, and at least one sibling must match.
-
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
-
# the criteria described by the hash, and at least one sibling must match.
-
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts the
-
# keys:
-
# ** <tt>:count</tt>: either a number or a range which must equal (or
-
# include) the number of children that match.
-
# ** <tt>:less_than</tt>: the number of matching children must be less than
-
# this number.
-
# ** <tt>:greater_than</tt>: the number of matching children must be
-
# greater than this number.
-
# ** <tt>:only</tt>: another hash consisting of the keys to use
-
# to match on the children, and only matching children will be
-
# counted.
-
#
-
# Conditions are matched using the following algorithm:
-
#
-
# * if the condition is a string, it must be a substring of the value.
-
# * if the condition is a regexp, it must match the value.
-
# * if the condition is a number, the value must match number.to_s.
-
# * if the condition is +true+, the value must not be +nil+.
-
# * if the condition is +false+ or +nil+, the value must be +nil+.
-
#
-
# Usage:
-
#
-
# # test if the node is a "span" tag
-
# node.match tag: "span"
-
#
-
# # test if the node's parent is a "div"
-
# node.match parent: { tag: "div" }
-
#
-
# # test if any of the node's ancestors are "table" tags
-
# node.match ancestor: { tag: "table" }
-
#
-
# # test if any of the node's immediate children are "em" tags
-
# node.match child: { tag: "em" }
-
#
-
# # test if any of the node's descendants are "strong" tags
-
# node.match descendant: { tag: "strong" }
-
#
-
# # test if the node has between 2 and 4 span tags as immediate children
-
# node.match children: { count: 2..4, only: { tag: "span" } }
-
#
-
# # get funky: test to see if the node is a "div", has a "ul" ancestor
-
# # and an "li" parent (with "class" = "enum"), and whether or not it has
-
# # a "span" descendant that contains # text matching /hello world/:
-
# node.match tag: "div",
-
# ancestor: { tag: "ul" },
-
# parent: { tag: "li",
-
# attributes: { class: "enum" } },
-
# descendant: { tag: "span",
-
# child: /hello world/ }
-
1
def match(conditions)
-
636
conditions = validate_conditions(conditions)
-
# check content of child nodes
-
636
if conditions[:content]
-
66
if children.empty?
-
10
return false unless match_condition("", conditions[:content])
-
else
-
180
return false unless children.find { |child| child.match(conditions[:content]) }
-
end
-
end
-
-
# test the name
-
589
return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]
-
-
# test attributes
-
401
(conditions[:attributes] || {}).each do |key, value|
-
139
return false unless match_condition(self[key], value)
-
end
-
-
# test parent
-
313
return false unless parent.match(conditions[:parent]) if conditions[:parent]
-
-
# test children
-
359
return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]
-
-
# test ancestors
-
268
if conditions[:ancestor]
-
16
return false unless catch :found do
-
16
p = self
-
16
throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
-
end
-
end
-
-
# test descendants
-
255
if conditions[:descendant]
-
55
return false unless children.find do |child|
-
# test the child
-
child.match(conditions[:descendant]) ||
-
# test the child's descendants
-
99
child.match(:descendant => conditions[:descendant])
-
end
-
end
-
-
# count children
-
207
if opts = conditions[:children]
-
55
matches = children.select do |c|
-
166
(c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
-
end
-
-
78
matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
-
55
opts.each do |key, value|
-
60
next if key == :only
-
55
case key
-
when :count
-
38
if Integer === value
-
37
return false if matches.length != value
-
else
-
1
return false unless value.include?(matches.length)
-
end
-
when :less_than
-
3
return false unless matches.length < value
-
when :greater_than
-
14
return false unless matches.length > value
-
else raise "unknown count condition #{key}"
-
end
-
end
-
end
-
-
# test siblings
-
165
if conditions[:sibling] || conditions[:before] || conditions[:after]
-
61
siblings = parent ? parent.children : []
-
61
self_index = siblings.index(self)
-
-
61
if conditions[:sibling]
-
21
return false unless siblings.detect do |s|
-
85
s != self && s.match(conditions[:sibling])
-
end
-
end
-
-
43
if conditions[:before]
-
17
return false unless siblings[self_index+1..-1].detect do |s|
-
27
s != self && s.match(conditions[:before])
-
end
-
end
-
-
28
if conditions[:after]
-
23
return false unless siblings[0,self_index].detect do |s|
-
39
s != self && s.match(conditions[:after])
-
end
-
end
-
end
-
-
111
true
-
end
-
-
1
def ==(node)
-
12629
return false unless super
-
12575
return false unless closing == node.closing && self.name == node.name
-
12575
attributes == node.attributes
-
end
-
-
1
private
-
# Match the given value to the given condition.
-
1
def match_condition(value, condition)
-
398
case condition
-
when String
-
307
value && value == condition
-
when Regexp
-
61
value && value.match(condition)
-
when Numeric
-
4
value == condition.to_s
-
when true
-
16
!value.nil?
-
when false, nil
-
10
value.nil?
-
else
-
false
-
end
-
end
-
end
-
end
-
1
require 'set'
-
1
require 'cgi'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
-
1
module HTML
-
1
class Sanitizer
-
1
def sanitize(text, options = {})
-
205
validate_options(options)
-
203
return text unless sanitizeable?(text)
-
140
tokenize(text, options).join
-
end
-
-
1
def sanitizeable?(text)
-
173
!(text.nil? || text.empty? || !text.index("<"))
-
end
-
-
1
protected
-
1
def tokenize(text, options)
-
140
tokenizer = HTML::Tokenizer.new(text)
-
140
result = []
-
140
while token = tokenizer.next
-
786
node = Node.parse(nil, 0, 0, token, false)
-
786
process_node node, result, options
-
end
-
140
result
-
end
-
-
1
def process_node(node, result, options)
-
result << node.to_s
-
end
-
-
1
def validate_options(options)
-
205
if options[:tags] && !options[:tags].is_a?(Enumerable)
-
1
raise ArgumentError, "You should pass :tags as an Enumerable"
-
end
-
-
204
if options[:attributes] && !options[:attributes].is_a?(Enumerable)
-
1
raise ArgumentError, "You should pass :attributes as an Enumerable"
-
end
-
end
-
end
-
-
1
class FullSanitizer < Sanitizer
-
1
def sanitize(text, options = {})
-
74
result = super
-
# strip any comments, and if they have a newline at the end (ie. line with
-
# only a comment) strip that too
-
74
result = result.gsub(/<!--(.*?)-->[\n]?/m, "") if (result && result =~ /<!--(.*?)-->[\n]?/m)
-
# Recurse - handle all dirty nested tags
-
74
result == text ? result : sanitize(result, options)
-
end
-
-
1
def process_node(node, result, options)
-
139
result << node.to_s if node.class == HTML::Text
-
end
-
end
-
-
1
class LinkSanitizer < FullSanitizer
-
1
cattr_accessor :included_tags, :instance_writer => false
-
1
self.included_tags = Set.new(%w(a href))
-
-
1
def sanitizeable?(text)
-
30
!(text.nil? || text.empty? || !((text.index("<a") || text.index("<href")) && text.index(">")))
-
end
-
-
1
protected
-
1
def process_node(node, result, options)
-
96
result << node.to_s unless node.is_a?(HTML::Tag) && included_tags.include?(node.name)
-
end
-
end
-
-
1
class WhiteListSanitizer < Sanitizer
-
1
[:protocol_separator, :uri_attributes, :allowed_attributes, :allowed_tags, :allowed_protocols, :bad_tags,
-
:allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties].each do |attr|
-
9
class_attribute attr, :instance_writer => false
-
end
-
-
# A regular expression of the valid characters used to separate protocols like
-
# the ':' in 'http://foo.com'
-
1
self.protocol_separator = /:|(�*58)|(p)|(%|%)3A/
-
-
# Specifies a Set of HTML attributes that can have URIs.
-
1
self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc))
-
-
# Specifies a Set of 'bad' tags that the #sanitize helper will remove completely, as opposed
-
# to just escaping harmless tags like <font>
-
1
self.bad_tags = Set.new(%w(script))
-
-
# Specifies the default Set of tags that the #sanitize helper will allow unscathed.
-
1
self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub
-
sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr
-
acronym a img blockquote del ins))
-
-
# Specifies the default Set of html attributes that the #sanitize helper will leave
-
# in the allowed tag.
-
1
self.allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr))
-
-
# Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
-
1
self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto
-
feed svn urn aim rsync tag ssh sftp rtsp afs))
-
-
# Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
-
1
self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse
-
border-color border-left-color border-right-color border-top-color clear color cursor direction display
-
elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height
-
overflow pause pause-after pause-before pitch pitch-range richness speak speak-header speak-numeral speak-punctuation
-
speech-rate stress text-align text-decoration text-indent unicode-bidi vertical-align voice-family volume white-space
-
width))
-
-
# Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
-
1
self.allowed_css_keywords = Set.new(%w(auto aqua black block blue bold both bottom brown center
-
collapse dashed dotted fuchsia gray green !important italic left lime maroon medium none navy normal
-
nowrap olive pointer purple red right solid silver teal top transparent underline white yellow))
-
-
# Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers.
-
1
self.shorthand_css_properties = Set.new(%w(background border margin padding))
-
-
# Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
-
1
def sanitize_css(style)
-
# disallow urls
-
6
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
-
-
# gauntlet
-
if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ ||
-
6
style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*$/
-
3
return ''
-
end
-
-
3
clean = []
-
3
style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val|
-
25
if allowed_css_properties.include?(prop.downcase)
-
9
clean << prop + ': ' + val + ';'
-
elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
-
8
unless val.split().any? do |keyword|
-
!allowed_css_keywords.include?(keyword) &&
-
6
keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
-
end
-
6
clean << prop + ': ' + val + ';'
-
end
-
end
-
end
-
3
clean.join(' ')
-
end
-
-
1
protected
-
1
def tokenize(text, options)
-
105
options[:parent] = []
-
105
options[:attributes] ||= allowed_attributes
-
105
options[:tags] ||= allowed_tags
-
105
super
-
end
-
-
1
def process_node(node, result, options)
-
result << case node
-
when HTML::Tag
-
288
if node.closing == :close
-
129
options[:parent].shift
-
else
-
159
options[:parent].unshift node.name
-
end
-
-
288
process_attributes_for node, options
-
-
288
options[:tags].include?(node.name) ? node : nil
-
else
-
263
bad_tags.include?(options[:parent].first) ? nil : node.to_s.gsub(/</, "<")
-
551
end
-
end
-
-
1
def process_attributes_for(node, options)
-
288
return unless node.attributes
-
159
node.attributes.keys.each do |attr_name|
-
145
value = node.attributes[attr_name].to_s
-
-
145
if !options[:attributes].include?(attr_name) || contains_bad_protocols?(attr_name, value)
-
80
node.attributes.delete(attr_name)
-
else
-
65
node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(CGI::unescapeHTML(value))
-
end
-
end
-
end
-
-
1
def contains_bad_protocols?(attr_name, value)
-
uri_attributes.include?(attr_name) &&
-
181
(value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
-
# Under MIT and/or CC By license.
-
#++
-
-
1
module HTML
-
-
# Selects HTML elements using CSS 2 selectors.
-
#
-
# The +Selector+ class uses CSS selector expressions to match and select
-
# HTML elements.
-
#
-
# For example:
-
# selector = HTML::Selector.new "form.login[action=/login]"
-
# creates a new selector that matches any +form+ element with the class
-
# +login+ and an attribute +action+ with the value <tt>/login</tt>.
-
#
-
# === Matching Elements
-
#
-
# Use the #match method to determine if an element matches the selector.
-
#
-
# For simple selectors, the method returns an array with that element,
-
# or +nil+ if the element does not match. For complex selectors (see below)
-
# the method returns an array with all matched elements, of +nil+ if no
-
# match found.
-
#
-
# For example:
-
# if selector.match(element)
-
# puts "Element is a login form"
-
# end
-
#
-
# === Selecting Elements
-
#
-
# Use the #select method to select all matching elements starting with
-
# one element and going through all children in depth-first order.
-
#
-
# This method returns an array of all matching elements, an empty array
-
# if no match is found
-
#
-
# For example:
-
# selector = HTML::Selector.new "input[type=text]"
-
# matches = selector.select(element)
-
# matches.each do |match|
-
# puts "Found text field with name #{match.attributes['name']}"
-
# end
-
#
-
# === Expressions
-
#
-
# Selectors can match elements using any of the following criteria:
-
# * <tt>name</tt> -- Match an element based on its name (tag name).
-
# For example, <tt>p</tt> to match a paragraph. You can use <tt>*</tt>
-
# to match any element.
-
# * <tt>#</tt><tt>id</tt> -- Match an element based on its identifier (the
-
# <tt>id</tt> attribute). For example, <tt>#</tt><tt>page</tt>.
-
# * <tt>.class</tt> -- Match an element based on its class name, all
-
# class names if more than one specified.
-
# * <tt>[attr]</tt> -- Match an element that has the specified attribute.
-
# * <tt>[attr=value]</tt> -- Match an element that has the specified
-
# attribute and value. (More operators are supported see below)
-
# * <tt>:pseudo-class</tt> -- Match an element based on a pseudo class,
-
# such as <tt>:nth-child</tt> and <tt>:empty</tt>.
-
# * <tt>:not(expr)</tt> -- Match an element that does not match the
-
# negation expression.
-
#
-
# When using a combination of the above, the element name comes first
-
# followed by identifier, class names, attributes, pseudo classes and
-
# negation in any order. Do not separate these parts with spaces!
-
# Space separation is used for descendant selectors.
-
#
-
# For example:
-
# selector = HTML::Selector.new "form.login[action=/login]"
-
# The matched element must be of type +form+ and have the class +login+.
-
# It may have other classes, but the class +login+ is required to match.
-
# It must also have an attribute called +action+ with the value
-
# <tt>/login</tt>.
-
#
-
# This selector will match the following element:
-
# <form class="login form" method="post" action="/login">
-
# but will not match the element:
-
# <form method="post" action="/logout">
-
#
-
# === Attribute Values
-
#
-
# Several operators are supported for matching attributes:
-
# * <tt>name</tt> -- The element must have an attribute with that name.
-
# * <tt>name=value</tt> -- The element must have an attribute with that
-
# name and value.
-
# * <tt>name^=value</tt> -- The attribute value must start with the
-
# specified value.
-
# * <tt>name$=value</tt> -- The attribute value must end with the
-
# specified value.
-
# * <tt>name*=value</tt> -- The attribute value must contain the
-
# specified value.
-
# * <tt>name~=word</tt> -- The attribute value must contain the specified
-
# word (space separated).
-
# * <tt>name|=word</tt> -- The attribute value must start with specified
-
# word.
-
#
-
# For example, the following two selectors match the same element:
-
# #my_id
-
# [id=my_id]
-
# and so do the following two selectors:
-
# .my_class
-
# [class~=my_class]
-
#
-
# === Alternatives, siblings, children
-
#
-
# Complex selectors use a combination of expressions to match elements:
-
# * <tt>expr1 expr2</tt> -- Match any element against the second expression
-
# if it has some parent element that matches the first expression.
-
# * <tt>expr1 > expr2</tt> -- Match any element against the second expression
-
# if it is the child of an element that matches the first expression.
-
# * <tt>expr1 + expr2</tt> -- Match any element against the second expression
-
# if it immediately follows an element that matches the first expression.
-
# * <tt>expr1 ~ expr2</tt> -- Match any element against the second expression
-
# that comes after an element that matches the first expression.
-
# * <tt>expr1, expr2</tt> -- Match any element against the first expression,
-
# or against the second expression.
-
#
-
# Since children and sibling selectors may match more than one element given
-
# the first element, the #match method may return more than one match.
-
#
-
# === Pseudo classes
-
#
-
# Pseudo classes were introduced in CSS 3. They are most often used to select
-
# elements in a given position:
-
# * <tt>:root</tt> -- Match the element only if it is the root element
-
# (no parent element).
-
# * <tt>:empty</tt> -- Match the element only if it has no child elements,
-
# and no text content.
-
# * <tt>:content(string)</tt> -- Match the element only if it has <tt>string</tt>
-
# as its text content (ignoring leading and trailing whitespace).
-
# * <tt>:only-child</tt> -- Match the element if it is the only child (element)
-
# of its parent element.
-
# * <tt>:only-of-type</tt> -- Match the element if it is the only child (element)
-
# of its parent element and its type.
-
# * <tt>:first-child</tt> -- Match the element if it is the first child (element)
-
# of its parent element.
-
# * <tt>:first-of-type</tt> -- Match the element if it is the first child (element)
-
# of its parent element of its type.
-
# * <tt>:last-child</tt> -- Match the element if it is the last child (element)
-
# of its parent element.
-
# * <tt>:last-of-type</tt> -- Match the element if it is the last child (element)
-
# of its parent element of its type.
-
# * <tt>:nth-child(b)</tt> -- Match the element if it is the b-th child (element)
-
# of its parent element. The value <tt>b</tt> specifies its index, starting with 1.
-
# * <tt>:nth-child(an+b)</tt> -- Match the element if it is the b-th child (element)
-
# in each group of <tt>a</tt> child elements of its parent element.
-
# * <tt>:nth-child(-an+b)</tt> -- Match the element if it is the first child (element)
-
# in each group of <tt>a</tt> child elements, up to the first <tt>b</tt> child
-
# elements of its parent element.
-
# * <tt>:nth-child(odd)</tt> -- Match element in the odd position (i.e. first, third).
-
# Same as <tt>:nth-child(2n+1)</tt>.
-
# * <tt>:nth-child(even)</tt> -- Match element in the even position (i.e. second,
-
# fourth). Same as <tt>:nth-child(2n+2)</tt>.
-
# * <tt>:nth-of-type(..)</tt> -- As above, but only counts elements of its type.
-
# * <tt>:nth-last-child(..)</tt> -- As above, but counts from the last child.
-
# * <tt>:nth-last-of-type(..)</tt> -- As above, but counts from the last child and
-
# only elements of its type.
-
# * <tt>:not(selector)</tt> -- Match the element only if the element does not
-
# match the simple selector.
-
#
-
# As you can see, <tt>:nth-child</tt> pseudo class and its variant can get quite
-
# tricky and the CSS specification doesn't do a much better job explaining it.
-
# But after reading the examples and trying a few combinations, it's easy to
-
# figure out.
-
#
-
# For example:
-
# table tr:nth-child(odd)
-
# Selects every second row in the table starting with the first one.
-
#
-
# div p:nth-child(4)
-
# Selects the fourth paragraph in the +div+, but not if the +div+ contains
-
# other elements, since those are also counted.
-
#
-
# div p:nth-of-type(4)
-
# Selects the fourth paragraph in the +div+, counting only paragraphs, and
-
# ignoring all other elements.
-
#
-
# div p:nth-of-type(-n+4)
-
# Selects the first four paragraphs, ignoring all others.
-
#
-
# And you can always select an element that matches one set of rules but
-
# not another using <tt>:not</tt>. For example:
-
# p:not(.post)
-
# Matches all paragraphs that do not have the class <tt>.post</tt>.
-
#
-
# === Substitution Values
-
#
-
# You can use substitution with identifiers, class names and element values.
-
# A substitution takes the form of a question mark (<tt>?</tt>) and uses the
-
# next value in the argument list following the CSS expression.
-
#
-
# The substitution value may be a string or a regular expression. All other
-
# values are converted to strings.
-
#
-
# For example:
-
# selector = HTML::Selector.new "#?", /^\d+$/
-
# matches any element whose identifier consists of one or more digits.
-
#
-
# See http://www.w3.org/TR/css3-selectors/
-
1
class Selector
-
-
-
# An invalid selector.
-
1
class InvalidSelectorError < StandardError #:nodoc:
-
end
-
-
-
1
class << self
-
-
# :call-seq:
-
# Selector.for_class(cls) => selector
-
#
-
# Creates a new selector for the given class name.
-
1
def for_class(cls)
-
self.new([".?", cls])
-
end
-
-
-
# :call-seq:
-
# Selector.for_id(id) => selector
-
#
-
# Creates a new selector for the given id.
-
1
def for_id(id)
-
self.new(["#?", id])
-
end
-
-
end
-
-
-
# :call-seq:
-
# Selector.new(string, [values ...]) => selector
-
#
-
# Creates a new selector from a CSS 2 selector expression.
-
#
-
# The first argument is the selector expression. All other arguments
-
# are used for value substitution.
-
#
-
# Throws InvalidSelectorError is the selector expression is invalid.
-
1
def initialize(selector, *values)
-
470
raise ArgumentError, "CSS expression cannot be empty" if selector.empty?
-
470
@source = ""
-
470
values = values[0] if values.size == 1 && values[0].is_a?(Array)
-
-
# We need a copy to determine if we failed to parse, and also
-
# preserve the original pass by-ref statement.
-
470
statement = selector.strip.dup
-
-
# Create a simple selector, along with negation.
-
2342
simple_selector(statement, values).each { |name, value| instance_variable_set("@#{name}", value) }
-
-
468
@alternates = []
-
468
@depends = nil
-
-
# Alternative selector.
-
468
if statement.sub!(/^\s*,\s*/, "")
-
10
second = Selector.new(statement, values)
-
10
@alternates << second
-
# If there are alternate selectors, we group them in the top selector.
-
10
if alternates = second.instance_variable_get(:@alternates)
-
10
second.instance_variable_set(:@alternates, [])
-
10
@alternates.concat alternates
-
end
-
10
@source << " , " << second.to_s
-
# Sibling selector: create a dependency into second selector that will
-
# match element immediately following this one.
-
458
elsif statement.sub!(/^\s*\+\s*/, "")
-
14
second = next_selector(statement, values)
-
14
@depends = lambda do |element, first|
-
18
if element = next_element(element)
-
17
second.match(element, first)
-
end
-
end
-
14
@source << " + " << second.to_s
-
# Adjacent selector: create a dependency into second selector that will
-
# match all elements following this one.
-
444
elsif statement.sub!(/^\s*~\s*/, "")
-
2
second = next_selector(statement, values)
-
2
@depends = lambda do |element, first|
-
2
matches = []
-
2
while element = next_element(element)
-
3
if subset = second.match(element, first)
-
3
if first && !subset.empty?
-
matches << subset.first
-
break
-
else
-
3
matches.concat subset
-
end
-
end
-
end
-
2
matches.empty? ? nil : matches
-
end
-
2
@source << " ~ " << second.to_s
-
# Child selector: create a dependency into second selector that will
-
# match a child element of this one.
-
442
elsif statement.sub!(/^\s*>\s*/, "")
-
37
second = next_selector(statement, values)
-
37
@depends = lambda do |element, first|
-
42
matches = []
-
42
element.children.each do |child|
-
54
if child.tag? && subset = second.match(child, first)
-
35
if first && !subset.empty?
-
matches << subset.first
-
break
-
else
-
35
matches.concat subset
-
end
-
end
-
end
-
42
matches.empty? ? nil : matches
-
end
-
37
@source << " > " << second.to_s
-
# Descendant selector: create a dependency into second selector that
-
# will match all descendant elements of this one. Note,
-
405
elsif statement =~ /^\s+\S+/ && statement != selector
-
28
second = next_selector(statement, values)
-
28
@depends = lambda do |element, first|
-
48
matches = []
-
48
stack = element.children.reverse
-
48
while node = stack.pop
-
374
next unless node.tag?
-
143
if subset = second.match(node, first)
-
51
if first && !subset.empty?
-
matches << subset.first
-
break
-
else
-
51
matches.concat subset
-
end
-
elsif children = node.children
-
92
stack.concat children.reverse
-
end
-
end
-
48
matches.empty? ? nil : matches
-
end
-
28
@source << " " << second.to_s
-
else
-
# The last selector is where we check that we parsed
-
# all the parts.
-
377
unless statement.empty? || statement.strip.empty?
-
1
raise ArgumentError, "Invalid selector: #{statement}"
-
end
-
end
-
end
-
-
-
# :call-seq:
-
# match(element, first?) => array or nil
-
#
-
# Matches an element against the selector.
-
#
-
# For a simple selector this method returns an array with the
-
# element if the element matches, nil otherwise.
-
#
-
# For a complex selector (sibling and descendant) this method
-
# returns an array with all matching elements, nil if no match is
-
# found.
-
#
-
# Use +first_only=true+ if you are only interested in the first element.
-
#
-
# For example:
-
# if selector.match(element)
-
# puts "Element is a login form"
-
# end
-
1
def match(element, first_only = false)
-
# Match element if no element name or element name same as element name
-
1659
if matched = (!@tag_name || @tag_name == element.name)
-
# No match if one of the attribute matches failed
-
913
for attr in @attributes
-
599
if element.attributes[attr[0]] !~ attr[1]
-
227
matched = false
-
227
break
-
end
-
end
-
end
-
-
# Pseudo class matches (nth-child, empty, etc).
-
1659
if matched
-
686
for pseudo in @pseudo
-
197
unless pseudo.call(element)
-
100
matched = false
-
100
break
-
end
-
end
-
end
-
-
# Negation. Same rules as above, but we fail if a match is made.
-
1659
if matched && @negation
-
586
for negation in @negation
-
27
if negation[:tag_name] == element.name
-
2
matched = false
-
else
-
25
for attr in negation[:attributes]
-
17
if element.attributes[attr[0]] =~ attr[1]
-
8
matched = false
-
8
break
-
end
-
end
-
end
-
27
if matched
-
17
for pseudo in negation[:pseudo]
-
4
if pseudo.call(element)
-
2
matched = false
-
2
break
-
end
-
end
-
end
-
27
break unless matched
-
end
-
end
-
-
# If element matched but depends on another element (child,
-
# sibling, etc), apply the dependent matches instead.
-
1659
if matched && @depends
-
110
matches = @depends.call(element, first_only)
-
else
-
1549
matches = matched ? [element] : nil
-
end
-
-
# If this selector is part of the group, try all the alternative
-
# selectors (unless first_only).
-
1659
if !first_only || !matches
-
1659
@alternates.each do |alternate|
-
22
break if matches && first_only
-
22
if subset = alternate.match(element, first_only)
-
8
if matches
-
matches.concat subset
-
else
-
8
matches = subset
-
end
-
end
-
end
-
end
-
-
1659
matches
-
end
-
-
-
# :call-seq:
-
# select(root) => array
-
#
-
# Selects and returns an array with all matching elements, beginning
-
# with one node and traversing through all children depth-first.
-
# Returns an empty array if no match is found.
-
#
-
# The root node may be any element in the document, or the document
-
# itself.
-
#
-
# For example:
-
# selector = HTML::Selector.new "input[type=text]"
-
# matches = selector.select(element)
-
# matches.each do |match|
-
# puts "Found text field with name #{match.attributes['name']}"
-
# end
-
1
def select(root)
-
376
matches = []
-
376
stack = [root]
-
376
while node = stack.pop
-
2737
if node.tag? && subset = match(node, false)
-
453
subset.each do |match|
-
642
matches << match unless matches.any? { |item| item.equal?(match) }
-
end
-
elsif children = node.children
-
2284
stack.concat children.reverse
-
end
-
end
-
376
matches
-
end
-
-
-
# Similar to #select but returns the first matching element. Returns +nil+
-
# if no element matches the selector.
-
1
def select_first(root)
-
stack = [root]
-
while node = stack.pop
-
if node.tag? && subset = match(node, true)
-
return subset.first if !subset.empty?
-
elsif children = node.children
-
stack.concat children.reverse
-
end
-
end
-
nil
-
end
-
-
-
1
def to_s #:nodoc:
-
305
@source
-
end
-
-
-
# Return the next element after this one. Skips sibling text nodes.
-
#
-
# With the +name+ argument, returns the next element with that name,
-
# skipping other sibling elements.
-
1
def next_element(element, name = nil)
-
23
if siblings = element.parent.children
-
23
found = false
-
23
siblings.each do |node|
-
64
if node.equal?(element)
-
23
found = true
-
elsif found && node.tag?
-
20
return node if (name.nil? || node.name == name)
-
end
-
end
-
end
-
nil
-
end
-
-
-
1
protected
-
-
-
# Creates a simple selector given the statement and array of
-
# substitution values.
-
#
-
# Returns a hash with the values +tag_name+, +attributes+,
-
# +pseudo+ (classes) and +negation+.
-
#
-
# Called the first time with +can_negate+ true to allow
-
# negation. Called a second time with false since negation
-
# cannot be negated.
-
1
def simple_selector(statement, values, can_negate = true)
-
485
tag_name = nil
-
485
attributes = []
-
485
pseudo = []
-
485
negation = []
-
-
# Element name. (Note that in negation, this can come at
-
# any order, but for simplicity we allow if only first).
-
485
statement.sub!(/^(\*|[[:alpha:]][\w\-]*)/) do |match|
-
409
match.strip!
-
409
tag_name = match.downcase unless match == "*"
-
409
@source << match
-
409
"" # Remove
-
end
-
-
# Get identifier, class, attribute name, pseudo or negation.
-
485
while true
-
# Element identifier.
-
914
next if statement.sub!(/^#(\?|[\w\-]+)/) do |match|
-
74
id = $1
-
74
if id == "?"
-
6
id = values.shift
-
end
-
74
@source << "##{id}"
-
74
id = Regexp.new("^#{Regexp.escape(id.to_s)}$") unless id.is_a?(Regexp)
-
74
attributes << ["id", id]
-
74
"" # Remove
-
end
-
-
# Class name.
-
840
next if statement.sub!(/^\.([\w\-]+)/) do |match|
-
24
class_name = $1
-
24
@source << ".#{class_name}"
-
24
class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
-
24
attributes << ["class", class_name]
-
24
"" # Remove
-
end
-
-
# Attribute value.
-
816
next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match|
-
233
name, equality, value = $1, $2, $3
-
233
if value == "?"
-
32
value = values.shift
-
else
-
# Handle single and double quotes.
-
201
value.strip!
-
201
if (value[0] == ?" || value[0] == ?') && value[0] == value[-1]
-
4
value = value[1..-2]
-
end
-
end
-
233
@source << "[#{name}#{equality}'#{value}']"
-
233
attributes << [name.downcase.strip, attribute_match(equality, value)]
-
233
"" # Remove
-
end
-
-
# Root element only.
-
583
next if statement.sub!(/^:root/) do |match|
-
pseudo << lambda do |element|
-
29
element.parent.nil? || !element.parent.tag?
-
24
end
-
24
@source << ":root"
-
24
"" # Remove
-
end
-
-
# Nth-child including last and of-type.
-
559
next if statement.sub!(/^:nth-(last-)?(child|of-type)\((odd|even|(\d+|\?)|(-?\d*|\?)?n([+\-]\d+|\?)?)\)/) do |match|
-
32
reverse = $1 == "last-"
-
32
of_type = $2 == "of-type"
-
32
@source << ":nth-#{$1}#{$2}("
-
32
case $3
-
when "odd"
-
3
pseudo << nth_child(2, 1, of_type, reverse)
-
3
@source << "odd)"
-
when "even"
-
1
pseudo << nth_child(2, 2, of_type, reverse)
-
1
@source << "even)"
-
when /^(\d+|\?)$/ # b only
-
8
b = ($1 == "?" ? values.shift : $1).to_i
-
8
pseudo << nth_child(0, b, of_type, reverse)
-
8
@source << "#{b})"
-
when /^(-?\d*|\?)?n([+\-]\d+|\?)?$/
-
20
a = ($1 == "?" ? values.shift :
-
$1 == "" ? 1 : $1 == "-" ? -1 : $1).to_i
-
20
b = ($2 == "?" ? values.shift : $2).to_i
-
20
pseudo << nth_child(a, b, of_type, reverse)
-
20
@source << (b >= 0 ? "#{a}n+#{b})" : "#{a}n#{b})")
-
else
-
raise ArgumentError, "Invalid nth-child #{match}"
-
end
-
32
"" # Remove
-
end
-
# First/last child (of type).
-
527
next if statement.sub!(/^:(first|last)-(child|of-type)/) do |match|
-
14
reverse = $1 == "last"
-
14
of_type = $2 == "of-type"
-
14
pseudo << nth_child(0, 1, of_type, reverse)
-
14
@source << ":#{$1}-#{$2}"
-
14
"" # Remove
-
end
-
# Only child (of type).
-
513
next if statement.sub!(/^:only-(child|of-type)/) do |match|
-
5
of_type = $1 == "of-type"
-
5
pseudo << only_child(of_type)
-
5
@source << ":only-#{$1}"
-
5
"" # Remove
-
end
-
-
# Empty: no child elements or meaningful content (whitespaces
-
# are ignored).
-
508
next if statement.sub!(/^:empty/) do |match|
-
pseudo << lambda do |element|
-
3
empty = true
-
3
for child in element.children
-
3
if child.tag? || !child.content.strip.empty?
-
1
empty = false
-
1
break
-
end
-
end
-
3
empty
-
3
end
-
3
@source << ":empty"
-
3
"" # Remove
-
end
-
# Content: match the text content of the element, stripping
-
# leading and trailing spaces.
-
505
next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do |match|
-
7
content = $1
-
7
if content == "?"
-
2
content = values.shift
-
elsif (content[0] == ?" || content[0] == ?') && content[0] == content[-1]
-
2
content = content[1..-2]
-
end
-
7
@source << ":content('#{content}')"
-
7
content = Regexp.new("^#{Regexp.escape(content.to_s)}$") unless content.is_a?(Regexp)
-
pseudo << lambda do |element|
-
7
text = ""
-
7
for child in element.children
-
7
unless child.tag?
-
7
text << child.content
-
end
-
end
-
7
text.strip =~ content
-
7
end
-
7
"" # Remove
-
end
-
-
# Negation. Create another simple selector to handle it.
-
498
if statement.sub!(/^:not\(\s*/, "")
-
16
raise ArgumentError, "Double negatives are not missing feature" unless can_negate
-
15
@source << ":not("
-
15
negation << simple_selector(statement, values, false)
-
14
raise ArgumentError, "Negation not closed" unless statement.sub!(/^\s*\)/, "")
-
13
@source << ")"
-
13
next
-
end
-
-
# No match: moving on.
-
482
break
-
end
-
-
# Return hash. The keys are mapped to instance variables.
-
482
{:tag_name=>tag_name, :attributes=>attributes, :pseudo=>pseudo, :negation=>negation}
-
end
-
-
-
# Create a regular expression to match an attribute value based
-
# on the equality operator (=, ^=, |=, etc).
-
1
def attribute_match(equality, value)
-
233
regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
-
233
case equality
-
when "=" then
-
# Match the attribute value in full
-
222
Regexp.new("^#{regexp}$")
-
when "~=" then
-
# Match a space-separated word within the attribute value
-
2
Regexp.new("(^|\s)#{regexp}($|\s)")
-
when "^="
-
# Match the beginning of the attribute value
-
1
Regexp.new("^#{regexp}")
-
when "$="
-
# Match the end of the attribute value
-
1
Regexp.new("#{regexp}$")
-
when "*="
-
# Match substring of the attribute value
-
1
regexp.is_a?(Regexp) ? regexp : Regexp.new(regexp)
-
when "|=" then
-
# Match the first space-separated item of the attribute value
-
2
Regexp.new("^#{regexp}($|\s)")
-
else
-
4
raise InvalidSelectorError, "Invalid operation/value" unless value.empty?
-
# Match all attributes values (existence check)
-
4
//
-
end
-
end
-
-
-
# Returns a lambda that can match an element against the nth-child
-
# pseudo class, given the following arguments:
-
# * +a+ -- Value of a part.
-
# * +b+ -- Value of b part.
-
# * +of_type+ -- True to test only elements of this type (of-type).
-
# * +reverse+ -- True to count in reverse order (last-).
-
1
def nth_child(a, b, of_type, reverse)
-
# a = 0 means select at index b, if b = 0 nothing selected
-
58
return lambda { |element| false } if a == 0 && b == 0
-
# a < 0 and b < 0 will never match against an index
-
47
return lambda { |element| false } if a < 0 && b < 0
-
42
b = a + b + 1 if b < 0 # b < 0 just picks last element from each group
-
42
b -= 1 unless b == 0 # b == 0 is same as b == 1, otherwise zero based
-
42
lambda do |element|
-
# Element must be inside parent element.
-
141
return false unless element.parent && element.parent.tag?
-
139
index = 0
-
# Get siblings, reverse if counting from last.
-
139
siblings = element.parent.children
-
139
siblings = siblings.reverse if reverse
-
# Match element name if of-type, otherwise ignore name.
-
139
name = of_type ? element.name : nil
-
139
found = false
-
139
for child in siblings
-
# Skip text nodes/comments.
-
322
if child.tag? && (name == nil || child.name == name)
-
310
if a == 0
-
# Shortcut when a == 0 no need to go past count
-
113
if index == b
-
51
found = child.equal?(element)
-
51
break
-
end
-
elsif a < 0
-
# Only look for first b elements
-
47
break if index > b
-
39
if child.equal?(element)
-
12
found = (index % a) == 0
-
12
break
-
end
-
else
-
# Otherwise, break if child found and count == an+b
-
150
if child.equal?(element)
-
60
found = (index % a) == b
-
60
break
-
end
-
end
-
179
index += 1
-
end
-
end
-
139
found
-
end
-
end
-
-
-
# Creates a only child lambda. Pass +of-type+ to only look at
-
# elements of its type.
-
1
def only_child(of_type)
-
5
lambda do |element|
-
# Element must be inside parent element.
-
5
return false unless element.parent && element.parent.tag?
-
4
name = of_type ? element.name : nil
-
4
other = false
-
4
for child in element.parent.children
-
# Skip text nodes/comments.
-
7
if child.tag? && (name == nil || child.name == name)
-
5
unless child.equal?(element)
-
2
other = true
-
2
break
-
end
-
end
-
end
-
4
!other
-
end
-
end
-
-
-
# Called to create a dependent selector (sibling, descendant, etc).
-
# Passes the remainder of the statement that will be reduced to zero
-
# eventually, and array of substitution values.
-
#
-
# This method is called from four places, so it helps to put it here
-
# for reuse. The only logic deals with the need to detect comma
-
# separators (alternate) and apply them to the selector group of the
-
# top selector.
-
1
def next_selector(statement, values)
-
81
second = Selector.new(statement, values)
-
# If there are alternate selectors, we group them in the top selector.
-
81
if alternates = second.instance_variable_get(:@alternates)
-
81
second.instance_variable_set(:@alternates, [])
-
81
@alternates.concat alternates
-
end
-
81
second
-
end
-
-
end
-
-
-
# See HTML::Selector.new
-
1
def self.selector(statement, *values)
-
126
Selector.new(statement, *values)
-
end
-
-
-
1
class Tag
-
-
1
def select(selector, *values)
-
1
selector = HTML::Selector.new(selector, values)
-
1
selector.select(self)
-
end
-
-
end
-
-
end
-
#--
-
# Copyright (c) 2004-2012 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
1
require 'active_support'
-
1
require 'active_support/rails'
-
1
require 'active_model/version'
-
-
1
module ActiveModel
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :AttributeMethods
-
1
autoload :BlockValidator, 'active_model/validator'
-
1
autoload :Callbacks
-
1
autoload :Conversion
-
1
autoload :Dirty
-
1
autoload :EachValidator, 'active_model/validator'
-
1
autoload :ForbiddenAttributesProtection
-
1
autoload :Lint
-
1
autoload :Model
-
1
autoload :DeprecatedMassAssignmentSecurity
-
1
autoload :Name, 'active_model/naming'
-
1
autoload :Naming
-
1
autoload :Observer, 'active_model/observing'
-
1
autoload :Observing
-
1
autoload :SecurePassword
-
1
autoload :Serialization
-
1
autoload :TestCase
-
1
autoload :Translation
-
1
autoload :Validations
-
1
autoload :Validator
-
-
1
eager_autoload do
-
1
autoload :Errors
-
end
-
-
1
module Serializers
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :JSON
-
1
autoload :Xml
-
end
-
end
-
-
1
def eager_load!
-
super
-
ActiveModel::Serializer.eager_load!
-
end
-
end
-
-
1
ActiveSupport.on_load(:i18n) do
-
1
I18n.load_path << File.dirname(__FILE__) + '/active_model/locale/en.yml'
-
end
-
-
1
module ActiveModel
-
# Raised when an attribute is not defined.
-
#
-
# class User < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# user = User.first
-
# user.pets.select(:id).first.user_id
-
# # => ActiveModel::MissingAttributeError: missing attribute: user_id
-
1
class MissingAttributeError < NoMethodError
-
end
-
# == Active \Model Attribute Methods
-
#
-
# <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and
-
# suffixes to your methods as well as handling the creation of Active Record
-
# like class methods such as +table_name+.
-
#
-
# The requirements to implement ActiveModel::AttributeMethods are to:
-
#
-
# * <tt>include ActiveModel::AttributeMethods</tt> in your object.
-
# * Call each Attribute Method module method you want to add, such as
-
# +attribute_method_suffix+ or +attribute_method_prefix+.
-
# * Call +define_attribute_methods+ after the other methods are called.
-
# * Define the various generic +_attribute+ methods that you have declared.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::AttributeMethods
-
#
-
# attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
-
# attribute_method_suffix '_contrived?'
-
# attribute_method_prefix 'clear_'
-
# define_attribute_methods :name
-
#
-
# attr_accessor :name
-
#
-
# private
-
#
-
# def attribute_contrived?(attr)
-
# true
-
# end
-
#
-
# def clear_attribute(attr)
-
# send("#{attr}=", nil)
-
# end
-
#
-
# def reset_attribute_to_default!(attr)
-
# send("#{attr}=", 'Default Name')
-
# end
-
# end
-
#
-
# Note that whenever you include ActiveModel::AttributeMethods in your class,
-
# it requires you to implement an +attributes+ method which returns a hash
-
# with each attribute name in your model as hash key and the attribute value as
-
# hash value.
-
#
-
# Hash keys must be strings.
-
1
module AttributeMethods
-
1
extend ActiveSupport::Concern
-
-
1
NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
-
1
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
-
-
1
included do
-
1
class_attribute :attribute_aliases, :attribute_method_matchers, instance_writer: false
-
1
self.attribute_aliases = {}
-
1
self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
-
end
-
-
1
module ClassMethods
-
# Declares a method available for all attributes with the given prefix.
-
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
-
#
-
# #{prefix}#{attr}(*args, &block)
-
#
-
# to
-
#
-
# #{prefix}attribute(#{attr}, *args, &block)
-
#
-
# An instance method <tt>#{prefix}attribute</tt> must exist and accept
-
# at least the +attr+ argument.
-
#
-
# class Person
-
# include ActiveModel::AttributeMethods
-
#
-
# attr_accessor :name
-
# attribute_method_prefix 'clear_'
-
# define_attribute_methods :name
-
#
-
# private
-
#
-
# def clear_attribute(attr)
-
# send("#{attr}=", nil)
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = 'Bob'
-
# person.name # => "Bob"
-
# person.clear_name
-
# person.name # => nil
-
1
def attribute_method_prefix(*prefixes)
-
self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
-
undefine_attribute_methods
-
end
-
-
# Declares a method available for all attributes with the given suffix.
-
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
-
#
-
# #{attr}#{suffix}(*args, &block)
-
#
-
# to
-
#
-
# attribute#{suffix}(#{attr}, *args, &block)
-
#
-
# An <tt>attribute#{suffix}</tt> instance method must exist and accept at
-
# least the +attr+ argument.
-
#
-
# class Person
-
# include ActiveModel::AttributeMethods
-
#
-
# attr_accessor :name
-
# attribute_method_suffix '_short?'
-
# define_attribute_methods :name
-
#
-
# private
-
#
-
# def attribute_short?(attr)
-
# send(attr).length < 5
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = 'Bob'
-
# person.name # => "Bob"
-
# person.name_short? # => true
-
1
def attribute_method_suffix(*suffixes)
-
11
self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
-
4
undefine_attribute_methods
-
end
-
-
# Declares a method available for all attributes with the given prefix
-
# and suffix. Uses +method_missing+ and <tt>respond_to?</tt> to rewrite
-
# the method.
-
#
-
# #{prefix}#{attr}#{suffix}(*args, &block)
-
#
-
# to
-
#
-
# #{prefix}attribute#{suffix}(#{attr}, *args, &block)
-
#
-
# An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
-
# accept at least the +attr+ argument.
-
#
-
# class Person
-
# include ActiveModel::AttributeMethods
-
#
-
# attr_accessor :name
-
# attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
-
# define_attribute_methods :name
-
#
-
# private
-
#
-
# def reset_attribute_to_default!(attr)
-
# ...
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name # => 'Gem'
-
# person.reset_name_to_default!
-
# person.name # => 'Gemma'
-
1
def attribute_method_affix(*affixes)
-
2
self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
-
1
undefine_attribute_methods
-
end
-
-
-
# Allows you to make aliases for attributes.
-
#
-
# class Person
-
# include ActiveModel::AttributeMethods
-
#
-
# attr_accessor :name
-
# attribute_method_suffix '_short?'
-
# define_attribute_methods :name
-
#
-
# alias_attribute :nickname, :name
-
#
-
# private
-
#
-
# def attribute_short?(attr)
-
# send(attr).length < 5
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = 'Bob'
-
# person.name # => "Bob"
-
# person.nickname # => "Bob"
-
# person.name_short? # => true
-
# person.nickname_short? # => true
-
1
def alias_attribute(new_name, old_name)
-
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
-
attribute_method_matchers.each do |matcher|
-
matcher_new = matcher.method_name(new_name).to_s
-
matcher_old = matcher.method_name(old_name).to_s
-
define_proxy_call false, self, matcher_new, matcher_old
-
end
-
end
-
-
# Declares the attributes that should be prefixed and suffixed by
-
# ActiveModel::AttributeMethods.
-
#
-
# To use, pass attribute names (as strings or symbols), be sure to declare
-
# +define_attribute_methods+ after you define any prefix, suffix or affix
-
# methods, or they will not hook in.
-
#
-
# class Person
-
# include ActiveModel::AttributeMethods
-
#
-
# attr_accessor :name, :age, :address
-
# attribute_method_prefix 'clear_'
-
#
-
# # Call to define_attribute_methods must appear after the
-
# # attribute_method_prefix, attribute_method_suffix or
-
# # attribute_method_affix declares.
-
# define_attribute_methods :name, :age, :address
-
#
-
# private
-
#
-
# def clear_attribute(attr)
-
# ...
-
# end
-
# end
-
1
def define_attribute_methods(*attr_names)
-
attr_names.flatten.each { |attr_name| define_attribute_method(attr_name) }
-
end
-
-
# Declares an attribute that should be prefixed and suffixed by
-
# ActiveModel::AttributeMethods.
-
#
-
# To use, pass an attribute name (as string or symbol), be sure to declare
-
# +define_attribute_method+ after you define any prefix, suffix or affix
-
# method, or they will not hook in.
-
#
-
# class Person
-
# include ActiveModel::AttributeMethods
-
#
-
# attr_accessor :name
-
# attribute_method_suffix '_short?'
-
#
-
# # Call to define_attribute_method must appear after the
-
# # attribute_method_prefix, attribute_method_suffix or
-
# # attribute_method_affix declares.
-
# define_attribute_method :name
-
#
-
# private
-
#
-
# def attribute_short?(attr)
-
# send(attr).length < 5
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = 'Bob'
-
# person.name # => "Bob"
-
# person.name_short? # => true
-
1
def define_attribute_method(attr_name)
-
attribute_method_matchers.each do |matcher|
-
method_name = matcher.method_name(attr_name)
-
-
unless instance_method_already_implemented?(method_name)
-
generate_method = "define_method_#{matcher.method_missing_target}"
-
-
if respond_to?(generate_method, true)
-
send(generate_method, attr_name)
-
else
-
define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
-
end
-
end
-
end
-
attribute_method_matchers_cache.clear
-
end
-
-
# Removes all the previously dynamically defined methods from the class.
-
#
-
# class Person
-
# include ActiveModel::AttributeMethods
-
#
-
# attr_accessor :name
-
# attribute_method_suffix '_short?'
-
# define_attribute_method :name
-
#
-
# private
-
#
-
# def attribute_short?(attr)
-
# send(attr).length < 5
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = 'Bob'
-
# person.name_short? # => true
-
#
-
# Person.undefine_attribute_methods
-
#
-
# person.name_short? # => NoMethodError
-
1
def undefine_attribute_methods
-
generated_attribute_methods.module_eval do
-
instance_methods.each { |m| undef_method(m) }
-
end
-
attribute_method_matchers_cache.clear
-
end
-
-
# Returns true if the attribute methods defined have been generated.
-
1
def generated_attribute_methods #:nodoc:
-
14
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
-
end
-
-
1
protected
-
1
def instance_method_already_implemented?(method_name) #:nodoc:
-
generated_attribute_methods.method_defined?(method_name)
-
end
-
-
1
private
-
# The methods +method_missing+ and +respond_to?+ of this module are
-
# invoked often in a typical rails, both of which invoke the method
-
# +match_attribute_method?+. The latter method iterates through an
-
# array doing regular expression matches, which results in a lot of
-
# object creations. Most of the times it returns a +nil+ match. As the
-
# match result is always the same given a +method_name+, this cache is
-
# used to alleviate the GC, which ultimately also speeds up the app
-
# significantly (in our case our test suite finishes 10% faster with
-
# this cache).
-
1
def attribute_method_matchers_cache #:nodoc:
-
@attribute_method_matchers_cache ||= {}
-
end
-
-
1
def attribute_method_matcher(method_name) #:nodoc:
-
attribute_method_matchers_cache.fetch(method_name) do |name|
-
# Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
-
# will match every time.
-
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
-
match = nil
-
matchers.detect { |method| match = method.match(name) }
-
attribute_method_matchers_cache[name] = match
-
end
-
end
-
-
# Define a method `name` in `mod` that dispatches to `send`
-
# using the given `extra` args. This fallbacks `define_method`
-
# and `send` if the given names cannot be compiled.
-
1
def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
-
defn = if name =~ NAME_COMPILABLE_REGEXP
-
"def #{name}(*args)"
-
else
-
"define_method(:'#{name}') do |*args|"
-
end
-
-
extra = (extra.map!(&:inspect) << "*args").join(", ")
-
-
target = if send =~ CALL_COMPILABLE_REGEXP
-
"#{"self." unless include_private}#{send}(#{extra})"
-
else
-
"send(:'#{send}', #{extra})"
-
end
-
-
mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
#{defn}
-
#{target}
-
end
-
RUBY
-
end
-
-
1
class AttributeMethodMatcher #:nodoc:
-
1
attr_reader :prefix, :suffix, :method_missing_target
-
-
1
AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
-
-
1
def initialize(options = {})
-
9
if options[:prefix] == '' || options[:suffix] == ''
-
message = "Specifying an empty prefix/suffix for an attribute method is no longer " \
-
"necessary. If the un-prefixed/suffixed version of the method has not been " \
-
"defined when `define_attribute_methods` is called, it will be defined " \
-
"automatically."
-
ActiveSupport::Deprecation.warn message
-
end
-
-
9
@prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '')
-
9
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
-
9
@method_missing_target = "#{@prefix}attribute#{@suffix}"
-
9
@method_name = "#{prefix}%s#{suffix}"
-
end
-
-
1
def match(method_name)
-
if @regex =~ method_name
-
AttributeMethodMatch.new(method_missing_target, $1, method_name)
-
end
-
end
-
-
1
def method_name(attr_name)
-
@method_name % attr_name
-
end
-
-
1
def plain?
-
prefix.empty? && suffix.empty?
-
end
-
end
-
end
-
-
# Allows access to the object attributes, which are held in the
-
# <tt>@attributes</tt> hash, as though they were first-class methods. So a
-
# Person class with a name attribute can use Person#name and Person#name=
-
# and never directly use the attributes hash -- except for multiple assigns
-
# with ActiveRecord#attributes=. A Milestone class can also ask
-
# Milestone#completed? to test that the completed attribute is not +nil+
-
# or 0.
-
#
-
# It's also possible to instantiate related objects, so a Client class
-
# belonging to the clients table with a +master_id+ foreign key can
-
# instantiate master through Client#master.
-
1
def method_missing(method, *args, &block)
-
if respond_to_without_attributes?(method, true)
-
super
-
else
-
match = match_attribute_method?(method.to_s)
-
match ? attribute_missing(match, *args, &block) : super
-
end
-
end
-
-
# attribute_missing is like method_missing, but for attributes. When method_missing is
-
# called we check to see if there is a matching attribute method. If so, we call
-
# attribute_missing to dispatch the attribute. This method can be overloaded to
-
# customise the behaviour.
-
1
def attribute_missing(match, *args, &block)
-
__send__(match.target, match.attr_name, *args, &block)
-
end
-
-
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
-
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
-
# which will all return +true+.
-
1
alias :respond_to_without_attributes? :respond_to?
-
1
def respond_to?(method, include_private_methods = false)
-
if super
-
true
-
elsif !include_private_methods && super(method, true)
-
# If we're here then we haven't found among non-private methods
-
# but found among all methods. Which means that the given method is private.
-
false
-
else
-
!match_attribute_method?(method.to_s).nil?
-
end
-
end
-
-
1
protected
-
1
def attribute_method?(attr_name) #:nodoc:
-
respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
-
end
-
-
1
private
-
# Returns a struct representing the matching attribute method.
-
# The struct's attributes are prefix, base and suffix.
-
1
def match_attribute_method?(method_name)
-
match = self.class.send(:attribute_method_matcher, method_name)
-
match if match && attribute_method?(match.attr_name)
-
end
-
-
1
def missing_attribute(attr_name, stack)
-
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
-
end
-
end
-
end
-
1
require 'active_support/callbacks'
-
-
1
module ActiveModel
-
# == Active \Model \Callbacks
-
#
-
# Provides an interface for any class to have Active Record like callbacks.
-
#
-
# Like the Active Record methods, the callback chain is aborted as soon as
-
# one of the methods in the chain returns +false+.
-
#
-
# First, extend ActiveModel::Callbacks from the class you are creating:
-
#
-
# class MyModel
-
# extend ActiveModel::Callbacks
-
# end
-
#
-
# Then define a list of methods that you want callbacks attached to:
-
#
-
# define_model_callbacks :create, :update
-
#
-
# This will provide all three standard callbacks (before, around and after)
-
# for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
-
# you need to wrap the methods you want callbacks on in a block so that the
-
# callbacks get a chance to fire:
-
#
-
# def create
-
# run_callbacks :create do
-
# # Your create action methods here
-
# end
-
# end
-
#
-
# Then in your class, you can use the +before_create+, +after_create+ and
-
# +around_create+ methods, just as you would in an Active Record module.
-
#
-
# before_create :action_before_create
-
#
-
# def action_before_create
-
# # Your code here
-
# end
-
#
-
# When defining an around callback remember to yield to the block, otherwise
-
# it won't be executed:
-
#
-
# around_create :log_status
-
#
-
# def log_status
-
# puts 'going to call the block...'
-
# yield
-
# puts 'block successfully called.'
-
# end
-
#
-
# You can choose not to have all three callbacks by passing a hash to the
-
# +define_model_callbacks+ method.
-
#
-
# define_model_callbacks :create, only: [:after, :before]
-
#
-
# Would only create the +after_create+ and +before_create+ callback methods in
-
# your class.
-
1
module Callbacks
-
1
def self.extended(base) #:nodoc:
-
2
base.class_eval do
-
2
include ActiveSupport::Callbacks
-
end
-
end
-
-
# define_model_callbacks accepts the same options +define_callbacks+ does,
-
# in case you want to overwrite a default. Besides that, it also accepts an
-
# <tt>:only</tt> option, where you can choose if you want all types (before,
-
# around or after) or just some.
-
#
-
# define_model_callbacks :initializer, only: :after
-
#
-
# Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
-
# on that method call. To get around this you can call the define_model_callbacks
-
# method as many times as you need.
-
#
-
# define_model_callbacks :create, only: :after
-
# define_model_callbacks :update, only: :before
-
# define_model_callbacks :destroy, only: :around
-
#
-
# Would create +after_create+, +before_update+ and +around_destroy+ methods
-
# only.
-
#
-
# You can pass in a class to before_<type>, after_<type> and around_<type>,
-
# in which case the callback will call that class's <action>_<type> method
-
# passing the object that the callback is being called on.
-
#
-
# class MyModel
-
# extend ActiveModel::Callbacks
-
# define_model_callbacks :create
-
#
-
# before_create AnotherClass
-
# end
-
#
-
# class AnotherClass
-
# def self.before_create( obj )
-
# # obj is the MyModel instance that the callback is being called on
-
# end
-
# end
-
1
def define_model_callbacks(*callbacks)
-
2
options = callbacks.extract_options!
-
2
options = {
-
:terminator => "result == false",
-
:skip_after_callbacks_if_terminated => true,
-
:scope => [:kind, :name],
-
:only => [:before, :around, :after]
-
}.merge!(options)
-
-
2
types = Array(options.delete(:only))
-
-
2
callbacks.each do |callback|
-
7
define_callbacks(callback, options)
-
-
7
types.each do |type|
-
15
send("_define_#{type}_model_callback", self, callback)
-
end
-
end
-
end
-
-
1
private
-
-
1
def _define_before_model_callback(klass, callback) #:nodoc:
-
4
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
-
def self.before_#{callback}(*args, &block)
-
set_callback(:#{callback}, :before, *args, &block)
-
end
-
CALLBACK
-
end
-
-
1
def _define_around_model_callback(klass, callback) #:nodoc:
-
4
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
-
def self.around_#{callback}(*args, &block)
-
set_callback(:#{callback}, :around, *args, &block)
-
end
-
CALLBACK
-
end
-
-
1
def _define_after_model_callback(klass, callback) #:nodoc:
-
7
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
-
def self.after_#{callback}(*args, &block)
-
options = args.extract_options!
-
options[:prepend] = true
-
options[:if] = Array(options[:if]) << "value != false"
-
set_callback(:#{callback}, :after, *(args << options), &block)
-
end
-
CALLBACK
-
end
-
end
-
end
-
1
require 'active_support/inflector'
-
-
1
module ActiveModel
-
# == Active \Model Conversions
-
#
-
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
-
#
-
# Let's take for example this non-persisted object.
-
#
-
# class ContactMessage
-
# include ActiveModel::Conversion
-
#
-
# # ContactMessage are never persisted in the DB
-
# def persisted?
-
# false
-
# end
-
# end
-
#
-
# cm = ContactMessage.new
-
# cm.to_model == cm # => true
-
# cm.to_key # => nil
-
# cm.to_param # => nil
-
# cm.to_partial_path # => "contact_messages/contact_message"
-
1
module Conversion
-
1
extend ActiveSupport::Concern
-
-
# If your object is already designed to implement all of the Active Model
-
# you can use the default <tt>:to_model</tt> implementation, which simply
-
# returns +self+.
-
#
-
# class Person
-
# include ActiveModel::Conversion
-
# end
-
#
-
# person = Person.new
-
# person.to_model == person # => true
-
#
-
# If your model does not act like an Active Model object, then you should
-
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
-
# your object with Active Model compliant methods.
-
1
def to_model
-
967
self
-
end
-
-
# Returns an Enumerable of all key attributes if any is set, regardless if
-
# the object is persisted or not. If there no key attributes, returns +nil+.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.create
-
# person.to_key # => [1]
-
1
def to_key
-
39
key = respond_to?(:id) && id
-
39
key ? [key] : nil
-
end
-
-
# Returns a +string+ representing the object's key suitable for use in URLs,
-
# or +nil+ if <tt>persisted?</tt> is +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.create
-
# person.to_param # => "1"
-
1
def to_param
-
23
persisted? ? to_key.join('-') : nil
-
end
-
-
# Returns a +string+ identifying the path associated with the object.
-
# ActionPack uses this to find a suitable partial to represent the object.
-
#
-
# class Person
-
# include ActiveModel::Conversion
-
# end
-
#
-
# person = Person.new
-
# person.to_partial_path # => "people/person"
-
1
def to_partial_path
-
21
self.class._to_partial_path
-
end
-
-
1
module ClassMethods #:nodoc:
-
# Provide a class level cache for #to_partial_path. This is an
-
# internal method and should not be accessed directly.
-
1
def _to_partial_path #:nodoc:
-
@_to_partial_path ||= begin
-
4
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self))
-
4
collection = ActiveSupport::Inflector.tableize(self)
-
4
"#{collection}/#{element}".freeze
-
21
end
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
1
module DeprecatedMassAssignmentSecurity # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods # :nodoc:
-
1
def attr_protected(*args)
-
raise "`attr_protected` is extracted out of Rails into a gem. " \
-
"Please use new recommended protection model for params " \
-
"or add `protected_attributes` to your Gemfile to use old one."
-
end
-
-
1
def attr_accessible(*args)
-
raise "`attr_accessible` is extracted out of Rails into a gem. " \
-
"Please use new recommended protection model for params " \
-
"or add `protected_attributes` to your Gemfile to use old one."
-
end
-
end
-
end
-
end
-
1
require 'active_model/attribute_methods'
-
1
require 'active_support/hash_with_indifferent_access'
-
1
require 'active_support/core_ext/object/duplicable'
-
-
1
module ActiveModel
-
# == Active \Model \Dirty
-
#
-
# Provides a way to track changes in your object in the same way as
-
# Active Record does.
-
#
-
# The requirements for implementing ActiveModel::Dirty are:
-
#
-
# * <tt>include ActiveModel::Dirty</tt> in your object.
-
# * Call <tt>define_attribute_methods</tt> passing each method you want to
-
# track.
-
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
-
# attribute.
-
#
-
# If you wish to also track previous changes on save or update, you need to
-
# add:
-
#
-
# @previously_changed = changes
-
#
-
# inside of your save or update method.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::Dirty
-
#
-
# define_attribute_methods :name
-
#
-
# def name
-
# @name
-
# end
-
#
-
# def name=(val)
-
# name_will_change! unless val == @name
-
# @name = val
-
# end
-
#
-
# def save
-
# @previously_changed = changes
-
# @changed_attributes.clear
-
# end
-
# end
-
#
-
# A newly instantiated object is unchanged:
-
#
-
# person = Person.find_by_name('Uncle Bob')
-
# person.changed? # => false
-
#
-
# Change the name:
-
#
-
# person.name = 'Bob'
-
# person.changed? # => true
-
# person.name_changed? # => true
-
# person.name_was # => "Uncle Bob"
-
# person.name_change # => ["Uncle Bob", "Bob"]
-
# person.name = 'Bill'
-
# person.name_change # => ["Uncle Bob", "Bill"]
-
#
-
# Save the changes:
-
#
-
# person.save
-
# person.changed? # => false
-
# person.name_changed? # => false
-
#
-
# Assigning the same value leaves the attribute unchanged:
-
#
-
# person.name = 'Bill'
-
# person.name_changed? # => false
-
# person.name_change # => nil
-
#
-
# Which attributes have changed?
-
#
-
# person.name = 'Bob'
-
# person.changed # => ["name"]
-
# person.changes # => {"name" => ["Bill", "Bob"]}
-
#
-
# If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
-
# to mark that the attribute is changing. Otherwise ActiveModel can't track
-
# changes to in-place attributes.
-
#
-
# person.name_will_change!
-
# person.name_change # => ["Bill", "Bill"]
-
# person.name << 'y'
-
# person.name_change # => ["Bill", "Billy"]
-
1
module Dirty
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::AttributeMethods
-
-
1
included do
-
1
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
-
1
attribute_method_affix :prefix => 'reset_', :suffix => '!'
-
end
-
-
# Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
-
#
-
# person.changed? # => false
-
# person.name = 'bob'
-
# person.changed? # => true
-
1
def changed?
-
changed_attributes.present?
-
end
-
-
# Returns an array with the name of the attributes with unsaved changes.
-
#
-
# person.changed # => []
-
# person.name = 'bob'
-
# person.changed # => ["name"]
-
1
def changed
-
changed_attributes.keys
-
end
-
-
# Returns a hash of changed attributes indicating their original
-
# and new values like <tt>attr => [original value, new value]</tt>.
-
#
-
# person.changes # => {}
-
# person.name = 'bob'
-
# person.changes # => { "name" => ["bill", "bob"] }
-
1
def changes
-
HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
-
end
-
-
# Returns a hash of attributes that were changed before the model was saved.
-
#
-
# person.name # => "bob"
-
# person.name = 'robert'
-
# person.save
-
# person.previous_changes # => {"name" => ["bob", "robert"]}
-
1
def previous_changes
-
@previously_changed
-
end
-
-
# Returns a hash of the attributes with unsaved changes indicating their original
-
# values like <tt>attr => original value</tt>.
-
#
-
# person.name # => "bob"
-
# person.name = 'robert'
-
# person.changed_attributes # => {"name" => "bob"}
-
1
def changed_attributes
-
@changed_attributes ||= {}
-
end
-
-
1
private
-
-
# Handle <tt>*_changed?</tt> for +method_missing+.
-
1
def attribute_changed?(attr)
-
changed_attributes.include?(attr)
-
end
-
-
# Handle <tt>*_change</tt> for +method_missing+.
-
1
def attribute_change(attr)
-
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
-
end
-
-
# Handle <tt>*_was</tt> for +method_missing+.
-
1
def attribute_was(attr)
-
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
-
end
-
-
# Handle <tt>*_will_change!</tt> for +method_missing+.
-
1
def attribute_will_change!(attr)
-
return if attribute_changed?(attr)
-
-
begin
-
value = __send__(attr)
-
value = value.duplicable? ? value.clone : value
-
rescue TypeError, NoMethodError
-
end
-
-
changed_attributes[attr] = value
-
end
-
-
# Handle <tt>reset_*!</tt> for +method_missing+.
-
1
def reset_attribute!(attr)
-
__send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr)
-
end
-
end
-
end
-
# -*- coding: utf-8 -*-
-
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
module ActiveModel
-
# == Active \Model \Errors
-
#
-
# Provides a modified +Hash+ that you can include in your object
-
# for handling error messages and interacting with Action Pack helpers.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
#
-
# # Required dependency for ActiveModel::Errors
-
# extend ActiveModel::Naming
-
#
-
# def initialize
-
# @errors = ActiveModel::Errors.new(self)
-
# end
-
#
-
# attr_accessor :name
-
# attr_reader :errors
-
#
-
# def validate!
-
# errors.add(:name, "can not be nil") if name == nil
-
# end
-
#
-
# # The following methods are needed to be minimally implemented
-
#
-
# def read_attribute_for_validation(attr)
-
# send(attr)
-
# end
-
#
-
# def Person.human_attribute_name(attr, options = {})
-
# attr
-
# end
-
#
-
# def Person.lookup_ancestors
-
# [self]
-
# end
-
#
-
# end
-
#
-
# The last three methods are required in your object for Errors to be
-
# able to generate error messages correctly and also handle multiple
-
# languages. Of course, if you extend your object with ActiveModel::Translation
-
# you will not need to implement the last two. Likewise, using
-
# ActiveModel::Validations will handle the validation related methods
-
# for you.
-
#
-
# The above allows you to do:
-
#
-
# p = Person.new
-
# person.validate! # => ["can not be nil"]
-
# person.errors.full_messages # => ["name can not be nil"]
-
# # etc..
-
1
class Errors
-
1
include Enumerable
-
-
1
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
-
-
1
attr_reader :messages
-
-
# Pass in the instance of the object that is using the errors object.
-
#
-
# class Person
-
# def initialize
-
# @errors = ActiveModel::Errors.new(self)
-
# end
-
# end
-
1
def initialize(base)
-
9
@base = base
-
9
@messages = {}
-
end
-
-
1
def initialize_dup(other) # :nodoc:
-
@messages = other.messages.dup
-
super
-
end
-
-
# Clear the error messages.
-
#
-
# person.errors.full_messages # => ["name can not be nil"]
-
# person.errors.clear
-
# person.errors.full_messages # => []
-
1
def clear
-
messages.clear
-
end
-
-
# Returns +true+ if the error messages include an error for the given key
-
# +attribute+, +false+ otherwise.
-
#
-
# person.errors.messages # => {:name=>["can not be nil"]}
-
# person.errors.include?(:name) # => true
-
# person.errors.include?(:age) # => false
-
1
def include?(attribute)
-
(v = messages[attribute]) && v.any?
-
end
-
# aliases include?
-
1
alias :has_key? :include?
-
-
# Get messages for +key+.
-
#
-
# person.errors.messages # => {:name=>["can not be nil"]}
-
# person.errors.get(:name) # => ["can not be nil"]
-
# person.errors.get(:age) # => nil
-
1
def get(key)
-
37
messages[key]
-
end
-
-
# Set messages for +key+ to +value+.
-
#
-
# person.errors.get(:name) # => ["can not be nil"]
-
# person.errors.set(:name, ["can't be nil"])
-
# person.errors.get(:name) # => ["can't be nil"]
-
1
def set(key, value)
-
27
messages[key] = value
-
end
-
-
# Delete messages for +key+. Returns the deleted messages.
-
#
-
# person.errors.get(:name) # => ["can not be nil"]
-
# person.errors.delete(:name) # => ["can not be nil"]
-
# person.errors.get(:name) # => nil
-
1
def delete(key)
-
messages.delete(key)
-
end
-
-
# When passed a symbol or a name of a method, returns an array of errors
-
# for the method.
-
#
-
# person.errors[:name] # => ["can not be nil"]
-
# person.errors['name'] # => ["can not be nil"]
-
1
def [](attribute)
-
37
get(attribute.to_sym) || set(attribute.to_sym, [])
-
end
-
-
# Adds to the supplied attribute the supplied error message.
-
#
-
# person.errors[:name] = "must be set"
-
# person.errors[:name] # => ['must be set']
-
1
def []=(attribute, error)
-
self[attribute] << error
-
end
-
-
# Iterates through each error key, value pair in the error messages hash.
-
# Yields the attribute and the error for that attribute. If the attribute
-
# has more than one error message, yields once for each error message.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.each do |attribute, error|
-
# # Will yield :name and "can't be blank"
-
# end
-
#
-
# person.errors.add(:name, "must be specified")
-
# person.errors.each do |attribute, error|
-
# # Will yield :name and "can't be blank"
-
# # then yield :name and "must be specified"
-
# end
-
1
def each
-
messages.each_key do |attribute|
-
self[attribute].each { |error| yield attribute, error }
-
end
-
end
-
-
# Returns the number of error messages.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.size # => 1
-
# person.errors.add(:name, "must be specified")
-
# person.errors.size # => 2
-
1
def size
-
values.flatten.size
-
end
-
-
# Returns all message values.
-
#
-
# person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
-
# person.errors.values # => [["can not be nil", "must be specified"]]
-
1
def values
-
messages.values
-
end
-
-
# Returns all message keys.
-
#
-
# person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
-
# person.errors.keys # => [:name]
-
1
def keys
-
messages.keys
-
end
-
-
# Returns an array of error messages, with the attribute name included.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.add(:name, "must be specified")
-
# person.errors.to_a # => ["name can't be blank", "name must be specified"]
-
1
def to_a
-
full_messages
-
end
-
-
# Returns the number of error messages.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.count # => 1
-
# person.errors.add(:name, "must be specified")
-
# person.errors.count # => 2
-
1
def count
-
to_a.size
-
end
-
-
# Returns +true+ if no errors are found, +false+ otherwise.
-
# If the error message is a string it can be empty.
-
#
-
# person.errors.full_messages # => ["name can not be nil"]
-
# person.errors.empty? # => false
-
1
def empty?
-
all? { |k, v| v && v.empty? && !v.is_a?(String) }
-
end
-
# aliases empty?
-
1
alias_method :blank?, :empty?
-
-
# Returns an xml formatted representation of the Errors hash.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.add(:name, "must be specified")
-
# person.errors.to_xml
-
# # =>
-
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
-
# # <errors>
-
# # <error>name can't be blank</error>
-
# # <error>name must be specified</error>
-
# # </errors>
-
1
def to_xml(options={})
-
to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
-
end
-
-
# Returns a Hash that can be used as the JSON representation for this
-
# object. You can pass the <tt>:full_messages</tt> option. This determines
-
# if the json object should contain full messages or not (false by default).
-
#
-
# person.as_json # => {:name=>["can not be nil"]}
-
# person.as_json(full_messages: true) # => {:name=>["name can not be nil"]}
-
1
def as_json(options=nil)
-
to_hash(options && options[:full_messages])
-
end
-
-
# Returns a Hash of attributes with their error messages. If +full_messages+
-
# is +true+, it will contain full messages (see +full_message+).
-
#
-
# person.to_hash # => {:name=>["can not be nil"]}
-
# person.to_hash(true) # => {:name=>["name can not be nil"]}
-
1
def to_hash(full_messages = false)
-
if full_messages
-
messages = {}
-
self.messages.each do |attribute, array|
-
messages[attribute] = array.map { |message| full_message(attribute, message) }
-
end
-
messages
-
else
-
self.messages.dup
-
end
-
end
-
-
# Adds +message+ to the error messages on +attribute+. More than one error
-
# can be added to the same +attribute+. If no +message+ is supplied,
-
# <tt>:invalid</tt> is assumed.
-
#
-
# person.errors.add(:name)
-
# # => ["is invalid"]
-
# person.errors.add(:name, 'must be implemented')
-
# # => ["is invalid", "must be implemented"]
-
#
-
# person.errors.messages
-
# # => {:name=>["must be implemented", "is invalid"]}
-
#
-
# If +message+ is a symbol, it will be translated using the appropriate
-
# scope (see +generate_message+).
-
#
-
# If +message+ is a proc, it will be called, allowing for things like
-
# <tt>Time.now</tt> to be used within an error.
-
#
-
# If the <tt>:strict</tt> option is set to true will raise
-
# ActiveModel::StrictValidationFailed instead of adding the error.
-
# <tt>:strict</tt> option can also be set to any other exception.
-
#
-
# person.errors.add(:name, nil, strict: true)
-
# # => ActiveModel::StrictValidationFailed: name is invalid
-
# person.errors.add(:name, nil, strict: NameIsInvalid)
-
# # => NameIsInvalid: name is invalid
-
#
-
# person.errors.messages # => {}
-
1
def add(attribute, message = nil, options = {})
-
message = normalize_message(attribute, message, options)
-
if exception = options[:strict]
-
exception = ActiveModel::StrictValidationFailed if exception == true
-
raise exception, full_message(attribute, message)
-
end
-
-
self[attribute] << message
-
end
-
-
# Will add an error message to each of the attributes in +attributes+
-
# that is empty.
-
#
-
# person.errors.add_on_empty(:name)
-
# person.errors.messages
-
# # => {:name=>["can't be empty"]}
-
1
def add_on_empty(attributes, options = {})
-
Array(attributes).each do |attribute|
-
value = @base.send(:read_attribute_for_validation, attribute)
-
is_empty = value.respond_to?(:empty?) ? value.empty? : false
-
add(attribute, :empty, options) if value.nil? || is_empty
-
end
-
end
-
-
# Will add an error message to each of the attributes in +attributes+ that
-
# is blank (using Object#blank?).
-
#
-
# person.errors.add_on_blank(:name)
-
# person.errors.messages
-
# # => {:name=>["can't be blank"]}
-
1
def add_on_blank(attributes, options = {})
-
Array(attributes).each do |attribute|
-
value = @base.send(:read_attribute_for_validation, attribute)
-
add(attribute, :blank, options) if value.blank?
-
end
-
end
-
-
# Returns +true+ if an error on the attribute with the given message is
-
# present, +false+ otherwise. +message+ is treated the same as for +add+.
-
#
-
# person.errors.add :name, :blank
-
# person.errors.added? :name, :blank # => true
-
1
def added?(attribute, message = nil, options = {})
-
message = normalize_message(attribute, message, options)
-
self[attribute].include? message
-
end
-
-
# Returns all the full error messages in an array.
-
#
-
# class Person
-
# validates_presence_of :name, :address, :email
-
# validates_length_of :name, in: 5..30
-
# end
-
#
-
# person = Person.create(address: '123 First St.')
-
# person.errors.full_messages
-
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
-
1
def full_messages
-
map { |attribute, message| full_message(attribute, message) }
-
end
-
-
# Returns a full message for a given attribute.
-
#
-
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
-
1
def full_message(attribute, message)
-
return message if attribute == :base
-
attr_name = attribute.to_s.tr('.', '_').humanize
-
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
-
I18n.t(:"errors.format", {
-
:default => "%{attribute} %{message}",
-
:attribute => attr_name,
-
:message => message
-
})
-
end
-
-
# Translates an error message in its default scope
-
# (<tt>activemodel.errors.messages</tt>).
-
#
-
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
-
# if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if
-
# that is not there also, it returns the translation of the default message
-
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
-
# name, translated attribute name and the value are available for
-
# interpolation.
-
#
-
# When using inheritance in your models, it will check all the inherited
-
# models too, but only if the model itself hasn't been found. Say you have
-
# <tt>class Admin < User; end</tt> and you wanted the translation for
-
# the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
-
# it looks for these translations:
-
#
-
# * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
-
# * <tt>activemodel.errors.models.admin.blank</tt>
-
# * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
-
# * <tt>activemodel.errors.models.user.blank</tt>
-
# * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
-
# * <tt>activemodel.errors.messages.blank</tt>
-
# * <tt>errors.attributes.title.blank</tt>
-
# * <tt>errors.messages.blank</tt>
-
1
def generate_message(attribute, type = :invalid, options = {})
-
type = options.delete(:message) if options[:message].is_a?(Symbol)
-
-
if @base.class.respond_to?(:i18n_scope)
-
defaults = @base.class.lookup_ancestors.map do |klass|
-
[ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
-
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
-
end
-
else
-
defaults = []
-
end
-
-
defaults << options.delete(:message)
-
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
-
defaults << :"errors.attributes.#{attribute}.#{type}"
-
defaults << :"errors.messages.#{type}"
-
-
defaults.compact!
-
defaults.flatten!
-
-
key = defaults.shift
-
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
-
-
options = {
-
:default => defaults,
-
:model => @base.class.model_name.human,
-
:attribute => @base.class.human_attribute_name(attribute),
-
:value => value
-
}.merge!(options)
-
-
I18n.translate(key, options)
-
end
-
-
1
private
-
1
def normalize_message(attribute, message, options)
-
message ||= :invalid
-
-
case message
-
when Symbol
-
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
-
when Proc
-
message.call
-
else
-
message
-
end
-
end
-
end
-
-
# Raised when a validation cannot be corrected by end users and are considered
-
# exceptional.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
#
-
# validates_presence_of :name, strict: true
-
# end
-
#
-
# person = Person.new
-
# person.name = nil
-
# person.valid?
-
# # => ActiveModel::StrictValidationFailed: Name can't be blank
-
1
class StrictValidationFailed < StandardError
-
end
-
end
-
1
module ActiveModel
-
# Raised when forbidden attributes are used for mass assignment.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# params = ActionController::Parameters.new(name: 'Bob')
-
# Person.new(params)
-
# # => ActiveModel::ForbiddenAttributesError
-
#
-
# params.permit!
-
# Person.new(params)
-
# # => #<Person id: nil, name: "Bob">
-
1
class ForbiddenAttributesError < StandardError
-
end
-
-
1
module ForbiddenAttributesProtection # :nodoc:
-
1
protected
-
1
def sanitize_for_mass_assignment(attributes)
-
if attributes.respond_to?(:permitted?) && !attributes.permitted?
-
raise ActiveModel::ForbiddenAttributesError
-
else
-
attributes
-
end
-
end
-
end
-
end
-
1
require 'active_support/inflector'
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/module/introspection'
-
-
1
module ActiveModel
-
1
class Name
-
1
include Comparable
-
-
1
attr_reader :singular, :plural, :element, :collection,
-
:singular_route_key, :route_key, :param_key, :i18n_key,
-
:name
-
-
1
alias_method :cache_key, :collection
-
-
##
-
# :method: ==
-
#
-
# :call-seq:
-
# ==(other)
-
#
-
# Equivalent to <tt>String#==</tt>. Returns +true+ if the class name and
-
# +other+ are equal, otherwise +false+.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name == 'BlogPost' # => true
-
# BlogPost.model_name == 'Blog Post' # => false
-
-
##
-
# :method: ===
-
#
-
# :call-seq:
-
# ===(other)
-
#
-
# Equivalent to <tt>#==</tt>.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name === 'BlogPost' # => true
-
# BlogPost.model_name === 'Blog Post' # => false
-
-
##
-
# :method: <=>
-
#
-
# :call-seq:
-
# ==(other)
-
#
-
# Equivalent to <tt>String#<=></tt>.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name <=> 'BlogPost' # => 0
-
# BlogPost.model_name <=> 'Blog' # => 1
-
# BlogPost.model_name <=> 'BlogPosts' # => -1
-
-
##
-
# :method: =~
-
#
-
# :call-seq:
-
# =~(regexp)
-
#
-
# Equivalent to <tt>String#=~</tt>. Match the class name against the given
-
# regexp. Returns the position where the match starts or +nil+ if there is
-
# no match.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name =~ /Post/ # => 4
-
# BlogPost.model_name =~ /\d/ # => nil
-
-
##
-
# :method: !~
-
#
-
# :call-seq:
-
# !~(regexp)
-
#
-
# Equivalent to <tt>String#!~</tt>. Match the class name against the given
-
# regexp. Returns +true+ if there is no match, otherwise +false+.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name !~ /Post/ # => false
-
# BlogPost.model_name !~ /\d/ # => true
-
-
##
-
# :method: eql?
-
#
-
# :call-seq:
-
# eql?(other)
-
#
-
# Equivalent to <tt>String#eql?</tt>. Returns +true+ if the class name and
-
# +other+ have the same length and content, otherwise +false+.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name.eql?('BlogPost') # => true
-
# BlogPost.model_name.eql?('Blog Post') # => false
-
-
##
-
# :method: to_s
-
#
-
# :call-seq:
-
# to_s()
-
#
-
# Returns the class name.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name.to_s # => "BlogPost"
-
-
##
-
# :method: to_str
-
#
-
# :call-seq:
-
# to_str()
-
#
-
# Equivalent to +to_s+.
-
1
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
-
:to_str, :to => :name
-
-
# Returns a new ActiveModel::Name instance. By default, the +namespace+
-
# and +name+ option will take the namespace and name of the given class
-
# respectively.
-
#
-
# module Foo
-
# class Bar
-
# end
-
# end
-
#
-
# ActiveModel::Name.new(Foo::Bar).to_s
-
# # => "Foo::Bar"
-
1
def initialize(klass, namespace = nil, name = nil)
-
17
@name = name || klass.name
-
-
17
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
-
-
17
@unnamespaced = @name.sub(/^#{namespace.name}::/, '') if namespace
-
17
@klass = klass
-
17
@singular = _singularize(@name)
-
17
@plural = ActiveSupport::Inflector.pluralize(@singular)
-
17
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
-
17
@human = ActiveSupport::Inflector.humanize(@element)
-
17
@collection = ActiveSupport::Inflector.tableize(@name)
-
17
@param_key = (namespace ? _singularize(@unnamespaced) : @singular)
-
17
@i18n_key = @name.underscore.to_sym
-
-
17
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
-
17
@singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
-
17
@route_key << "_index" if @plural == @singular
-
end
-
-
# Transform the model name into a more humane format, using I18n. By default,
-
# it will underscore then humanize the class name.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name.human # => "Blog post"
-
#
-
# Specify +options+ with additional translating options.
-
1
def human(options={})
-
return @human unless @klass.respond_to?(:lookup_ancestors) &&
-
4
@klass.respond_to?(:i18n_scope)
-
-
4
defaults = @klass.lookup_ancestors.map do |klass|
-
4
klass.model_name.i18n_key
-
end
-
-
4
defaults << options[:default] if options[:default]
-
4
defaults << @human
-
-
4
options = { :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults }.merge!(options.except(:default))
-
4
I18n.translate(defaults.shift, options)
-
end
-
-
1
private
-
-
1
def _singularize(string, replacement='_')
-
18
ActiveSupport::Inflector.underscore(string).tr('/', replacement)
-
end
-
end
-
-
# == Active \Model \Naming
-
#
-
# Creates a +model_name+ method on your object.
-
#
-
# To implement, just extend ActiveModel::Naming in your object:
-
#
-
# class BookCover
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BookCover.model_name # => "BookCover"
-
# BookCover.model_name.human # => "Book cover"
-
#
-
# BookCover.model_name.i18n_key # => :book_cover
-
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
-
#
-
# Providing the functionality that ActiveModel::Naming provides in your object
-
# is required to pass the Active Model Lint test. So either extending the
-
# provided method below, or rolling your own is required.
-
1
module Naming
-
# Returns an ActiveModel::Name object for module. It can be
-
# used to retrieve all kinds of naming-related information
-
# (See ActiveModel::Name for more information).
-
#
-
# class Person < ActiveModel::Model
-
# end
-
#
-
# Person.model_name # => Person
-
# Person.model_name.class # => ActiveModel::Name
-
# Person.model_name.singular # => "person"
-
# Person.model_name.plural # => "people"
-
1
def model_name
-
@_model_name ||= begin
-
11
namespace = self.parents.detect do |n|
-
13
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
-
end
-
11
ActiveModel::Name.new(self, namespace)
-
493
end
-
end
-
-
# Returns the plural class name of a record or class.
-
#
-
# ActiveModel::Naming.plural(post) # => "posts"
-
# ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
-
1
def self.plural(record_or_class)
-
model_name_from_record_or_class(record_or_class).plural
-
end
-
-
# Returns the singular class name of a record or class.
-
#
-
# ActiveModel::Naming.singular(post) # => "post"
-
# ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
-
1
def self.singular(record_or_class)
-
model_name_from_record_or_class(record_or_class).singular
-
end
-
-
# Identifies whether the class name of a record or class is uncountable.
-
#
-
# ActiveModel::Naming.uncountable?(Sheep) # => true
-
# ActiveModel::Naming.uncountable?(Post) # => false
-
1
def self.uncountable?(record_or_class)
-
plural(record_or_class) == singular(record_or_class)
-
end
-
-
# Returns string to use while generating route names. It differs for
-
# namespaced models regarding whether it's inside isolated engine.
-
#
-
# # For isolated engine:
-
# ActiveModel::Naming.singular_route_key(Blog::Post) #=> post
-
#
-
# # For shared engine:
-
# ActiveModel::Naming.singular_route_key(Blog::Post) #=> blog_post
-
1
def self.singular_route_key(record_or_class)
-
model_name_from_record_or_class(record_or_class).singular_route_key
-
end
-
-
# Returns string to use while generating route names. It differs for
-
# namespaced models regarding whether it's inside isolated engine.
-
#
-
# # For isolated engine:
-
# ActiveModel::Naming.route_key(Blog::Post) #=> posts
-
#
-
# # For shared engine:
-
# ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
-
#
-
# The route key also considers if the noun is uncountable and, in
-
# such cases, automatically appends _index.
-
1
def self.route_key(record_or_class)
-
model_name_from_record_or_class(record_or_class).route_key
-
end
-
-
# Returns string to use for params names. It differs for
-
# namespaced models regarding whether it's inside isolated engine.
-
#
-
# # For isolated engine:
-
# ActiveModel::Naming.param_key(Blog::Post) #=> post
-
#
-
# # For shared engine:
-
# ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
-
1
def self.param_key(record_or_class)
-
model_name_from_record_or_class(record_or_class).param_key
-
end
-
-
1
def self.model_name_from_record_or_class(record_or_class) #:nodoc:
-
if record_or_class.respond_to?(:model_name)
-
record_or_class.model_name
-
elsif record_or_class.respond_to?(:to_model)
-
record_or_class.to_model.class.model_name
-
else
-
record_or_class.class.model_name
-
end
-
end
-
1
private_class_method :model_name_from_record_or_class
-
end
-
end
-
1
require 'set'
-
-
1
module ActiveModel
-
# Stores the enabled/disabled state of individual observers for
-
# a particular model class.
-
1
class ObserverArray < Array
-
1
attr_reader :model_class
-
1
def initialize(model_class, *args) #:nodoc:
-
1
@model_class = model_class
-
1
super(*args)
-
end
-
-
# Returns +true+ if the given observer is disabled for the model class,
-
# +false+ otherwise.
-
1
def disabled_for?(observer) #:nodoc:
-
disabled_observers.include?(observer.class)
-
end
-
-
# Disables one or more observers. This supports multiple forms:
-
#
-
# ORM.observers.disable :all
-
# # => disables all observers for all models subclassed from
-
# # an ORM base class that includes ActiveModel::Observing
-
# # e.g. ActiveRecord::Base
-
#
-
# ORM.observers.disable :user_observer
-
# # => disables the UserObserver
-
#
-
# User.observers.disable AuditTrail
-
# # => disables the AuditTrail observer for User notifications.
-
# # Other models will still notify the AuditTrail observer.
-
#
-
# ORM.observers.disable :observer_1, :observer_2
-
# # => disables Observer1 and Observer2 for all models.
-
#
-
# User.observers.disable :all do
-
# # all user observers are disabled for
-
# # just the duration of the block
-
# end
-
1
def disable(*observers, &block)
-
set_enablement(false, observers, &block)
-
end
-
-
# Enables one or more observers. This supports multiple forms:
-
#
-
# ORM.observers.enable :all
-
# # => enables all observers for all models subclassed from
-
# # an ORM base class that includes ActiveModel::Observing
-
# # e.g. ActiveRecord::Base
-
#
-
# ORM.observers.enable :user_observer
-
# # => enables the UserObserver
-
#
-
# User.observers.enable AuditTrail
-
# # => enables the AuditTrail observer for User notifications.
-
# # Other models will not be affected (i.e. they will not
-
# # trigger notifications to AuditTrail if previously disabled)
-
#
-
# ORM.observers.enable :observer_1, :observer_2
-
# # => enables Observer1 and Observer2 for all models.
-
#
-
# User.observers.enable :all do
-
# # all user observers are enabled for
-
# # just the duration of the block
-
# end
-
#
-
# Note: all observers are enabled by default. This method is only
-
# useful when you have previously disabled one or more observers.
-
1
def enable(*observers, &block)
-
set_enablement(true, observers, &block)
-
end
-
-
1
protected
-
-
1
def disabled_observers #:nodoc:
-
@disabled_observers ||= Set.new
-
end
-
-
1
def observer_class_for(observer) #:nodoc:
-
return observer if observer.is_a?(Class)
-
-
if observer.respond_to?(:to_sym) # string/symbol
-
observer.to_s.camelize.constantize
-
else
-
raise ArgumentError, "#{observer} was not a class or a " +
-
"lowercase, underscored class name as expected."
-
end
-
end
-
-
1
def start_transaction #:nodoc:
-
disabled_observer_stack.push(disabled_observers.dup)
-
each_subclass_array do |array|
-
array.start_transaction
-
end
-
end
-
-
1
def disabled_observer_stack #:nodoc:
-
@disabled_observer_stack ||= []
-
end
-
-
1
def end_transaction #:nodoc:
-
@disabled_observers = disabled_observer_stack.pop
-
each_subclass_array do |array|
-
array.end_transaction
-
end
-
end
-
-
1
def transaction #:nodoc:
-
start_transaction
-
-
begin
-
yield
-
ensure
-
end_transaction
-
end
-
end
-
-
1
def each_subclass_array #:nodoc:
-
model_class.descendants.each do |subclass|
-
yield subclass.observers
-
end
-
end
-
-
1
def set_enablement(enabled, observers) #:nodoc:
-
if block_given?
-
transaction do
-
set_enablement(enabled, observers)
-
yield
-
end
-
else
-
observers = ActiveModel::Observer.descendants if observers == [:all]
-
observers.each do |obs|
-
klass = observer_class_for(obs)
-
-
unless klass < ActiveModel::Observer
-
raise ArgumentError.new("#{obs} does not refer to a valid observer")
-
end
-
-
if enabled
-
disabled_observers.delete(klass)
-
else
-
disabled_observers << klass
-
end
-
end
-
-
each_subclass_array do |array|
-
array.set_enablement(enabled, observers)
-
end
-
end
-
end
-
end
-
end
-
1
require 'singleton'
-
1
require 'active_model/observer_array'
-
1
require 'active_support/core_ext/module/aliasing'
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'active_support/core_ext/string/inflections'
-
1
require 'active_support/core_ext/enumerable'
-
1
require 'active_support/core_ext/object/try'
-
1
require 'active_support/descendants_tracker'
-
-
1
module ActiveModel
-
# == Active \Model Observers Activation
-
1
module Observing
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
extend ActiveSupport::DescendantsTracker
-
end
-
-
1
module ClassMethods
-
# Activates the observers assigned.
-
#
-
# class ORM
-
# include ActiveModel::Observing
-
# end
-
#
-
# # Calls PersonObserver.instance
-
# ORM.observers = :person_observer
-
#
-
# # Calls Cacher.instance and GarbageCollector.instance
-
# ORM.observers = :cacher, :garbage_collector
-
#
-
# # Same as above, just using explicit class references
-
# ORM.observers = Cacher, GarbageCollector
-
#
-
# Note: Setting this does not instantiate the observers yet.
-
# <tt>instantiate_observers</tt> is called during startup, and before
-
# each development request.
-
1
def observers=(*values)
-
observers.replace(values.flatten)
-
end
-
-
# Gets an array of observers observing this model. The array also provides
-
# +enable+ and +disable+ methods that allow you to selectively enable and
-
# disable observers (see ActiveModel::ObserverArray.enable and
-
# ActiveModel::ObserverArray.disable for more on this).
-
#
-
# class ORM
-
# include ActiveModel::Observing
-
# end
-
#
-
# ORM.observers = :cacher, :garbage_collector
-
# ORM.observers # => [:cacher, :garbage_collector]
-
# ORM.observers.class # => ActiveModel::ObserverArray
-
1
def observers
-
1
@observers ||= ObserverArray.new(self)
-
end
-
-
# Returns the current observer instances.
-
#
-
# class Foo
-
# include ActiveModel::Observing
-
#
-
# attr_accessor :status
-
# end
-
#
-
# class FooObserver < ActiveModel::Observer
-
# def on_spec(record, *args)
-
# record.status = true
-
# end
-
# end
-
#
-
# Foo.observers = FooObserver
-
# Foo.instantiate_observers
-
#
-
# Foo.observer_instances # => [#<FooObserver:0x007fc212c40820>]
-
1
def observer_instances
-
7
@observer_instances ||= []
-
end
-
-
# Instantiate the global observers.
-
#
-
# class Foo
-
# include ActiveModel::Observing
-
#
-
# attr_accessor :status
-
# end
-
#
-
# class FooObserver < ActiveModel::Observer
-
# def on_spec(record, *args)
-
# record.status = true
-
# end
-
# end
-
#
-
# Foo.observers = FooObserver
-
#
-
# foo = Foo.new
-
# foo.status = false
-
# foo.notify_observers(:on_spec)
-
# foo.status # => false
-
#
-
# Foo.instantiate_observers # => [FooObserver]
-
#
-
# foo = Foo.new
-
# foo.status = false
-
# foo.notify_observers(:on_spec)
-
# foo.status # => true
-
1
def instantiate_observers
-
observers.each { |o| instantiate_observer(o) }
-
end
-
-
# Add a new observer to the pool. The new observer needs to respond to
-
# <tt>update</tt>, otherwise it raises an +ArgumentError+ exception.
-
#
-
# class Foo
-
# include ActiveModel::Observing
-
# end
-
#
-
# class FooObserver < ActiveModel::Observer
-
# end
-
#
-
# Foo.add_observer(FooObserver.instance)
-
#
-
# Foo.observers_instance
-
# # => [#<FooObserver:0x007fccf55d9390>]
-
1
def add_observer(observer)
-
unless observer.respond_to? :update
-
raise ArgumentError, "observer needs to respond to 'update'"
-
end
-
observer_instances << observer
-
end
-
-
# Fires notifications to model's observers.
-
#
-
# def save
-
# notify_observers(:before_save)
-
# ...
-
# notify_observers(:after_save)
-
# end
-
#
-
# Custom notifications can be sent in a similar fashion:
-
#
-
# notify_observers(:custom_notification, :foo)
-
#
-
# This will call <tt>custom_notification</tt>, passing as arguments
-
# the current object and <tt>:foo</tt>.
-
1
def notify_observers(*args)
-
7
observer_instances.each { |observer| observer.update(*args) }
-
end
-
-
# Returns the total number of instantiated observers.
-
#
-
# class Foo
-
# include ActiveModel::Observing
-
#
-
# attr_accessor :status
-
# end
-
#
-
# class FooObserver < ActiveModel::Observer
-
# def on_spec(record, *args)
-
# record.status = true
-
# end
-
# end
-
#
-
# Foo.observers = FooObserver
-
# Foo.observers_count # => 0
-
# Foo.instantiate_observers
-
# Foo.observers_count # => 1
-
1
def observers_count
-
observer_instances.size
-
end
-
-
# <tt>count_observers</tt> is deprecated. Use #observers_count.
-
1
def count_observers
-
msg = "count_observers is deprecated in favor of observers_count"
-
ActiveSupport::Deprecation.warn msg
-
observers_count
-
end
-
-
1
protected
-
1
def instantiate_observer(observer) #:nodoc:
-
# string/symbol
-
if observer.respond_to?(:to_sym)
-
observer = observer.to_s.camelize.constantize
-
end
-
if observer.respond_to?(:instance)
-
observer.instance
-
else
-
raise ArgumentError,
-
"#{observer} must be a lowercase, underscored class name (or " +
-
"the class itself) responding to the method :instance. " +
-
"Example: Person.observers = :big_brother # calls " +
-
"BigBrother.instance"
-
end
-
end
-
-
# Notify observers when the observed class is subclassed.
-
1
def inherited(subclass) #:nodoc:
-
7
super
-
7
notify_observers :observed_class_inherited, subclass
-
end
-
end
-
-
# Notify a change to the list of observers.
-
#
-
# class Foo
-
# include ActiveModel::Observing
-
#
-
# attr_accessor :status
-
# end
-
#
-
# class FooObserver < ActiveModel::Observer
-
# def on_spec(record, *args)
-
# record.status = true
-
# end
-
# end
-
#
-
# Foo.observers = FooObserver
-
# Foo.instantiate_observers # => [FooObserver]
-
#
-
# foo = Foo.new
-
# foo.status = false
-
# foo.notify_observers(:on_spec)
-
# foo.status # => true
-
#
-
# See ActiveModel::Observing::ClassMethods.notify_observers for more
-
# information.
-
1
def notify_observers(method, *extra_args)
-
self.class.notify_observers(method, self, *extra_args)
-
end
-
end
-
-
# == Active \Model Observers
-
#
-
# Observer classes respond to life cycle callbacks to implement trigger-like
-
# behavior outside the original class. This is a great way to reduce the
-
# clutter that normally comes when the model class is burdened with
-
# functionality that doesn't pertain to the core responsibility of the
-
# class.
-
#
-
# class CommentObserver < ActiveModel::Observer
-
# def after_save(comment)
-
# Notifications.comment('admin@do.com', 'New comment was posted', comment).deliver
-
# end
-
# end
-
#
-
# This Observer sends an email when a <tt>Comment#save</tt> is finished.
-
#
-
# class ContactObserver < ActiveModel::Observer
-
# def after_create(contact)
-
# contact.logger.info('New contact added!')
-
# end
-
#
-
# def after_destroy(contact)
-
# contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
-
# end
-
# end
-
#
-
# This Observer uses logger to log when specific callbacks are triggered.
-
#
-
# == \Observing a class that can't be inferred
-
#
-
# Observers will by default be mapped to the class with which they share a
-
# name. So <tt>CommentObserver</tt> will be tied to observing <tt>Comment</tt>,
-
# <tt>ProductManagerObserver</tt> to <tt>ProductManager</tt>, and so on. If
-
# you want to name your observer differently than the class you're interested
-
# in observing, you can use the <tt>Observer.observe</tt> class method which
-
# takes either the concrete class (<tt>Product</tt>) or a symbol for that
-
# class (<tt>:product</tt>):
-
#
-
# class AuditObserver < ActiveModel::Observer
-
# observe :account
-
#
-
# def after_update(account)
-
# AuditTrail.new(account, 'UPDATED')
-
# end
-
# end
-
#
-
# If the audit observer needs to watch more than one kind of object, this can
-
# be specified with multiple arguments:
-
#
-
# class AuditObserver < ActiveModel::Observer
-
# observe :account, :balance
-
#
-
# def after_update(record)
-
# AuditTrail.new(record, 'UPDATED')
-
# end
-
# end
-
#
-
# The <tt>AuditObserver</tt> will now act on both updates to <tt>Account</tt>
-
# and <tt>Balance</tt> by treating them both as records.
-
#
-
# If you're using an Observer in a Rails application with Active Record, be
-
# sure to read about the necessary configuration in the documentation for
-
# ActiveRecord::Observer.
-
1
class Observer
-
1
include Singleton
-
1
extend ActiveSupport::DescendantsTracker
-
-
1
class << self
-
# Attaches the observer to the supplied model classes.
-
#
-
# class AuditObserver < ActiveModel::Observer
-
# observe :account, :balance
-
# end
-
#
-
# AuditObserver.observed_classes # => [Account, Balance]
-
1
def observe(*models)
-
models.flatten!
-
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
-
singleton_class.redefine_method(:observed_classes) { models }
-
end
-
-
# Returns an array of Classes to observe.
-
#
-
# AccountObserver.observed_classes # => [Account]
-
#
-
# You can override this instead of using the +observe+ helper.
-
#
-
# class AuditObserver < ActiveModel::Observer
-
# def self.observed_classes
-
# [Account, Balance]
-
# end
-
# end
-
1
def observed_classes
-
5
Array(observed_class)
-
end
-
-
# Returns the class observed by default. It's inferred from the observer's
-
# class name.
-
#
-
# PersonObserver.observed_class # => Person
-
# AccountObserver.observed_class # => Account
-
1
def observed_class
-
5
name[/(.*)Observer/, 1].try :constantize
-
end
-
end
-
-
# Start observing the declared classes and their subclasses.
-
# Called automatically by the instance method.
-
1
def initialize #:nodoc:
-
5
observed_classes.each { |klass| add_observer!(klass) }
-
end
-
-
1
def observed_classes #:nodoc:
-
5
self.class.observed_classes
-
end
-
-
# Send observed_method(object) if the method exists and
-
# the observer is enabled for the given object's class.
-
1
def update(observed_method, object, *extra_args, &block) #:nodoc:
-
return if !respond_to?(observed_method) || disabled_for?(object)
-
send(observed_method, object, *extra_args, &block)
-
end
-
-
# Special method sent by the observed class when it is inherited.
-
# Passes the new subclass.
-
1
def observed_class_inherited(subclass) #:nodoc:
-
self.class.observe(observed_classes + [subclass])
-
add_observer!(subclass)
-
end
-
-
1
protected
-
1
def add_observer!(klass) #:nodoc:
-
klass.add_observer(self)
-
end
-
-
# Returns true if notifications are disabled for this object.
-
1
def disabled_for?(object) #:nodoc:
-
klass = object.class
-
return false unless klass.respond_to?(:observers)
-
klass.observers.disabled_for?(self)
-
end
-
end
-
end
-
1
module ActiveModel
-
1
module SecurePassword
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Adds methods to set and authenticate against a BCrypt password.
-
# This mechanism requires you to have a password_digest attribute.
-
#
-
# Validations for presence of password on create, confirmation of password
-
# (using a +password_confirmation+ attribute) are automatically added. If
-
# you wish to turn off validations, pass <tt>validations: false</tt> as an
-
# argument. You can add more validations by hand if need be.
-
#
-
# You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use #has_secure_password:
-
#
-
# gem 'bcrypt-ruby', '~> 3.0.0'
-
#
-
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
-
#
-
# # Schema: User(name:string, password_digest:string)
-
# class User < ActiveRecord::Base
-
# has_secure_password
-
# end
-
#
-
# user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
-
# user.save # => false, password required
-
# user.password = 'mUc3m00RsqyRe'
-
# user.save # => false, confirmation doesn't match
-
# user.password_confirmation = 'mUc3m00RsqyRe'
-
# user.save # => true
-
# user.authenticate('notright') # => false
-
# user.authenticate('mUc3m00RsqyRe') # => user
-
# User.find_by_name('david').try(:authenticate, 'notright') # => false
-
# User.find_by_name('david').try(:authenticate, 'mUc3m00RsqyRe') # => user
-
1
def has_secure_password(options = {})
-
# Load bcrypt-ruby only when has_secure_password is used.
-
# This is to avoid ActiveModel (and by extension the entire framework)
-
# being dependent on a binary library.
-
gem 'bcrypt-ruby', '~> 3.0.0'
-
require 'bcrypt'
-
-
attr_reader :password
-
-
if options.fetch(:validations, true)
-
validates_confirmation_of :password
-
validates_presence_of :password, :on => :create
-
-
before_create { raise "Password digest missing on new record" if password_digest.blank? }
-
end
-
-
include InstanceMethodsOnActivation
-
-
if respond_to?(:attributes_protected_by_default)
-
def self.attributes_protected_by_default #:nodoc:
-
super + ['password_digest']
-
end
-
end
-
end
-
end
-
-
1
module InstanceMethodsOnActivation
-
# Returns +self+ if the password is correct, otherwise +false+.
-
#
-
# class User < ActiveRecord::Base
-
# has_secure_password validations: false
-
# end
-
#
-
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
-
# user.save
-
# user.authenticate('notright') # => false
-
# user.authenticate('mUc3m00RsqyRe') # => user
-
1
def authenticate(unencrypted_password)
-
BCrypt::Password.new(password_digest) == unencrypted_password && self
-
end
-
-
# Encrypts the password into the +password_digest+ attribute, only if the
-
# new password is not blank.
-
#
-
# class User < ActiveRecord::Base
-
# has_secure_password validations: false
-
# end
-
#
-
# user = User.new
-
# user.password = nil
-
# user.password_digest # => nil
-
# user.password = 'mUc3m00RsqyRe'
-
# user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
-
1
def password=(unencrypted_password)
-
unless unencrypted_password.blank?
-
@password = unencrypted_password
-
self.password_digest = BCrypt::Password.create(unencrypted_password)
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/hash/slice'
-
-
1
module ActiveModel
-
# == Active \Model \Serialization
-
#
-
# Provides a basic serialization to a serializable_hash for your object.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::Serialization
-
#
-
# attr_accessor :name
-
#
-
# def attributes
-
# {'name' => nil}
-
# end
-
# end
-
#
-
# Which would provide you with:
-
#
-
# person = Person.new
-
# person.serializable_hash # => {"name"=>nil}
-
# person.name = "Bob"
-
# person.serializable_hash # => {"name"=>"Bob"}
-
#
-
# You need to declare an attributes hash which contains the attributes you
-
# want to serialize. Attributes must be strings, not symbols. When called,
-
# serializable hash will use instance methods that match the name of the
-
# attributes hash's keys. In order to override this behavior, take a look at
-
# the private method +read_attribute_for_serialization+.
-
#
-
# Most of the time though, you will want to include the JSON or XML
-
# serializations. Both of these modules automatically include the
-
# <tt>ActiveModel::Serialization</tt> module, so there is no need to
-
# explicitly include it.
-
#
-
# A minimal implementation including XML and JSON would be:
-
#
-
# class Person
-
# include ActiveModel::Serializers::JSON
-
# include ActiveModel::Serializers::Xml
-
#
-
# attr_accessor :name
-
#
-
# def attributes
-
# {'name' => nil}
-
# end
-
# end
-
#
-
# Which would provide you with:
-
#
-
# person = Person.new
-
# person.serializable_hash # => {"name"=>nil}
-
# person.as_json # => {"name"=>nil}
-
# person.to_json # => "{\"name\":null}"
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
-
#
-
# person.name = "Bob"
-
# person.serializable_hash # => {"name"=>"Bob"}
-
# person.as_json # => {"name"=>"Bob"}
-
# person.to_json # => "{\"name\":\"Bob\"}"
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
-
#
-
# Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
-
# <tt>:include</tt>. The following are all valid examples:
-
#
-
# person.serializable_hash(only: 'name')
-
# person.serializable_hash(include: :address)
-
# person.serializable_hash(include: { address: { only: 'city' }})
-
1
module Serialization
-
# Returns a serialized hash of your object.
-
#
-
# class Person
-
# include ActiveModel::Serialization
-
#
-
# attr_accessor :name, :age
-
#
-
# def attributes
-
# {'name' => nil, 'age' => nil}
-
# end
-
#
-
# def capitalized_name
-
# name.capitalize
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = 'bob'
-
# person.age = 22
-
# person.serializable_hash # => {"name"=>"bob", "age"=>22}
-
# person.serializable_hash(only: :name) # => {"name"=>"bob"}
-
# person.serializable_hash(except: :name) # => {"age"=>22}
-
# person.serializable_hash(methods: :capitalized_name)
-
# # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
-
1
def serializable_hash(options = nil)
-
options ||= {}
-
-
attribute_names = attributes.keys
-
if only = options[:only]
-
attribute_names &= Array(only).map(&:to_s)
-
elsif except = options[:except]
-
attribute_names -= Array(except).map(&:to_s)
-
end
-
-
hash = {}
-
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
-
-
Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
-
-
serializable_add_includes(options) do |association, records, opts|
-
hash[association.to_s] = if records.respond_to?(:to_ary)
-
records.to_ary.map { |a| a.serializable_hash(opts) }
-
else
-
records.serializable_hash(opts)
-
end
-
end
-
-
hash
-
end
-
-
1
private
-
-
# Hook method defining how an attribute value should be retrieved for
-
# serialization. By default this is assumed to be an instance named after
-
# the attribute. Override this method in subclasses should you need to
-
# retrieve the value for a given attribute differently:
-
#
-
# class MyClass
-
# include ActiveModel::Validations
-
#
-
# def initialize(data = {})
-
# @data = data
-
# end
-
#
-
# def read_attribute_for_serialization(key)
-
# @data[key]
-
# end
-
# end
-
1
alias :read_attribute_for_serialization :send
-
-
# Add associations specified via the <tt>:include</tt> option.
-
#
-
# Expects a block that takes as arguments:
-
# +association+ - name of the association
-
# +records+ - the association record(s) to be serialized
-
# +opts+ - options for the association records
-
1
def serializable_add_includes(options = {}) #:nodoc:
-
return unless includes = options[:include]
-
-
unless includes.is_a?(Hash)
-
includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
-
end
-
-
includes.each do |association, opts|
-
if records = send(association)
-
yield association, records, opts
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/json'
-
-
1
module ActiveModel
-
1
module Serializers
-
# == Active Model JSON Serializer
-
1
module JSON
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Serialization
-
-
1
included do
-
1
extend ActiveModel::Naming
-
-
1
class_attribute :include_root_in_json
-
1
self.include_root_in_json = false
-
end
-
-
# Returns a hash representing the model. Some configuration can be
-
# passed through +options+.
-
#
-
# The option <tt>include_root_in_json</tt> controls the top-level behavior
-
# of +as_json+. If +true+, +as_json+ will emit a single root node named
-
# after the object's type. The default value for <tt>include_root_in_json</tt>
-
# option is +false+.
-
#
-
# user = User.find(1)
-
# user.as_json
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true}
-
#
-
# ActiveRecord::Base.include_root_in_json = true
-
#
-
# user.as_json
-
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true } }
-
#
-
# This behavior can also be achieved by setting the <tt>:root</tt> option
-
# to +true+ as in:
-
#
-
# user = User.find(1)
-
# user.as_json(root: true)
-
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true } }
-
#
-
# Without any +options+, the returned Hash will include all the model's
-
# attributes.
-
#
-
# user = User.find(1)
-
# user.as_json
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true}
-
#
-
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
-
# the attributes included, and work similar to the +attributes+ method.
-
#
-
# user.as_json(only: [:id, :name])
-
# # => { "id" => 1, "name" => "Konata Izumi" }
-
#
-
# user.as_json(except: [:id, :created_at, :age])
-
# # => { "name" => "Konata Izumi", "awesome" => true }
-
#
-
# To include the result of some method calls on the model use <tt>:methods</tt>:
-
#
-
# user.as_json(methods: :permalink)
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true,
-
# # "permalink" => "1-konata-izumi" }
-
#
-
# To include associations use <tt>:include</tt>:
-
#
-
# user.as_json(include: :posts)
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true,
-
# # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
-
# # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
-
#
-
# Second level and higher order associations work as well:
-
#
-
# user.as_json(include: { posts: {
-
# include: { comments: {
-
# only: :body } },
-
# only: :title } })
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true,
-
# # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
-
# # "title" => "Welcome to the weblog" },
-
# # { "comments" => [ { "body" => "Don't think too hard" } ],
-
# # "title" => "So I was thinking" } ] }
-
1
def as_json(options = nil)
-
root = if options && options.key?(:root)
-
options[:root]
-
else
-
include_root_in_json
-
end
-
-
if root
-
root = self.class.model_name.element if root == true
-
{ root => serializable_hash(options) }
-
else
-
serializable_hash(options)
-
end
-
end
-
-
# Sets the model +attributes+ from a JSON string. Returns +self+.
-
#
-
# class Person
-
# include ActiveModel::Serializers::JSON
-
#
-
# attr_accessor :name, :age, :awesome
-
#
-
# def attributes=(hash)
-
# hash.each do |key, value|
-
# instance_variable_set("@#{key}", value)
-
# end
-
# end
-
#
-
# def attributes
-
# instance_values
-
# end
-
# end
-
#
-
# json = { name: 'bob', age: 22, awesome:true }.to_json
-
# person = Person.new
-
# person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
-
# person.name # => "bob"
-
# person.age # => 22
-
# person.awesome # => true
-
#
-
# The default value for +include_root+ is +false+. You can change it to
-
# +true+ if the given JSON string includes a single root node.
-
#
-
# json = { person: { name: 'bob', age: 22, awesome:true } }.to_json
-
# person = Person.new
-
# person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
-
# person.name # => "bob"
-
# person.age # => 22
-
# person.awesome # => true
-
1
def from_json(json, include_root=include_root_in_json)
-
hash = ActiveSupport::JSON.decode(json)
-
hash = hash.values.first if include_root
-
self.attributes = hash
-
self
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/hash/conversions'
-
1
require 'active_support/core_ext/hash/slice'
-
-
1
module ActiveModel
-
1
module Serializers
-
# == Active Model XML Serializer
-
1
module Xml
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Serialization
-
-
1
included do
-
1
extend ActiveModel::Naming
-
end
-
-
1
class Serializer #:nodoc:
-
1
class Attribute #:nodoc:
-
1
attr_reader :name, :value, :type
-
-
1
def initialize(name, serializable, value)
-
@name, @serializable = name, serializable
-
value = value.in_time_zone if value.respond_to?(:in_time_zone)
-
@value = value
-
@type = compute_type
-
end
-
-
1
def decorations
-
decorations = {}
-
decorations[:encoding] = 'base64' if type == :binary
-
decorations[:type] = (type == :string) ? nil : type
-
decorations[:nil] = true if value.nil?
-
decorations
-
end
-
-
1
protected
-
-
1
def compute_type
-
return if value.nil?
-
type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
-
type ||= :string if value.respond_to?(:to_str)
-
type ||= :yaml
-
type
-
end
-
end
-
-
1
class MethodAttribute < Attribute #:nodoc:
-
end
-
-
1
attr_reader :options
-
-
1
def initialize(serializable, options = nil)
-
@serializable = serializable
-
@options = options ? options.dup : {}
-
end
-
-
1
def serializable_hash
-
@serializable.serializable_hash(@options.except(:include))
-
end
-
-
1
def serializable_collection
-
methods = Array(options[:methods]).map(&:to_s)
-
serializable_hash.map do |name, value|
-
name = name.to_s
-
if methods.include?(name)
-
self.class::MethodAttribute.new(name, @serializable, value)
-
else
-
self.class::Attribute.new(name, @serializable, value)
-
end
-
end
-
end
-
-
1
def serialize
-
require 'builder' unless defined? ::Builder
-
-
options[:indent] ||= 2
-
options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
-
-
@builder = options[:builder]
-
@builder.instruct! unless options[:skip_instruct]
-
-
root = (options[:root] || @serializable.class.model_name.element).to_s
-
root = ActiveSupport::XmlMini.rename_key(root, options)
-
-
args = [root]
-
args << {:xmlns => options[:namespace]} if options[:namespace]
-
args << {:type => options[:type]} if options[:type] && !options[:skip_types]
-
-
@builder.tag!(*args) do
-
add_attributes_and_methods
-
add_includes
-
add_extra_behavior
-
add_procs
-
yield @builder if block_given?
-
end
-
end
-
-
1
private
-
-
1
def add_extra_behavior
-
end
-
-
1
def add_attributes_and_methods
-
serializable_collection.each do |attribute|
-
key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
-
ActiveSupport::XmlMini.to_tag(key, attribute.value,
-
options.merge(attribute.decorations))
-
end
-
end
-
-
1
def add_includes
-
@serializable.send(:serializable_add_includes, options) do |association, records, opts|
-
add_associations(association, records, opts)
-
end
-
end
-
-
# TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
-
1
def add_associations(association, records, opts)
-
merged_options = opts.merge(options.slice(:builder, :indent))
-
merged_options[:skip_instruct] = true
-
-
[:skip_types, :dasherize, :camelize].each do |key|
-
merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
-
end
-
-
if records.respond_to?(:to_ary)
-
records = records.to_ary
-
-
tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
-
type = options[:skip_types] ? { } : {:type => "array"}
-
association_name = association.to_s.singularize
-
merged_options[:root] = association_name
-
-
if records.empty?
-
@builder.tag!(tag, type)
-
else
-
@builder.tag!(tag, type) do
-
records.each do |record|
-
if options[:skip_types]
-
record_type = {}
-
else
-
record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
-
record_type = {:type => record_class}
-
end
-
-
record.to_xml merged_options.merge(record_type)
-
end
-
end
-
end
-
else
-
merged_options[:root] = association.to_s
-
records.to_xml(merged_options)
-
end
-
end
-
-
1
def add_procs
-
if procs = options.delete(:procs)
-
Array(procs).each do |proc|
-
if proc.arity == 1
-
proc.call(options)
-
else
-
proc.call(options, @serializable)
-
end
-
end
-
end
-
end
-
end
-
-
# Returns XML representing the model. Configuration can be
-
# passed through +options+.
-
#
-
# Without any +options+, the returned XML string will include all the
-
# model's attributes.
-
#
-
# user = User.find(1)
-
# user.to_xml
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <user>
-
# <id type="integer">1</id>
-
# <name>David</name>
-
# <age type="integer">16</age>
-
# <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
-
# </user>
-
#
-
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
-
# attributes included, and work similar to the +attributes+ method.
-
#
-
# To include the result of some method calls on the model use <tt>:methods</tt>.
-
#
-
# To include associations use <tt>:include</tt>.
-
#
-
# For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
-
1
def to_xml(options = {}, &block)
-
Serializer.new(self, options).serialize(&block)
-
end
-
-
# Sets the model +attributes+ from a JSON string. Returns +self+.
-
#
-
# class Person
-
# include ActiveModel::Serializers::Xml
-
#
-
# attr_accessor :name, :age, :awesome
-
#
-
# def attributes=(hash)
-
# hash.each do |key, value|
-
# instance_variable_set("@#{key}", value)
-
# end
-
# end
-
#
-
# def attributes
-
# instance_values
-
# end
-
# end
-
#
-
# xml = { name: 'bob', age: 22, awesome:true }.to_xml
-
# person = Person.new
-
# person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
-
# person.name # => "bob"
-
# person.age # => 22
-
# person.awesome # => true
-
1
def from_xml(xml)
-
self.attributes = Hash.from_xml(xml).values.first
-
self
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
# == Active \Model \Translation
-
#
-
# Provides integration between your object and the Rails internationalization
-
# (i18n) framework.
-
#
-
# A minimal implementation could be:
-
#
-
# class TranslatedPerson
-
# extend ActiveModel::Translation
-
# end
-
#
-
# TranslatedPerson.human_attribute_name('my_attribute')
-
# # => "My attribute"
-
#
-
# This also provides the required class methods for hooking into the
-
# Rails internationalization API, including being able to define a
-
# class based +i18n_scope+ and +lookup_ancestors+ to find translations in
-
# parent classes.
-
1
module Translation
-
1
include ActiveModel::Naming
-
-
# Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
-
1
def i18n_scope
-
28
:activemodel
-
end
-
-
# When localizing a string, it goes through the lookup returned by this
-
# method, which is used in ActiveModel::Name#human,
-
# ActiveModel::Errors#full_messages and
-
# ActiveModel::Translation#human_attribute_name.
-
1
def lookup_ancestors
-
420
self.ancestors.select { |x| x.respond_to?(:model_name) }
-
end
-
-
# Transforms attribute names into a more human format, such as "First name"
-
# instead of "first_name".
-
#
-
# Person.human_attribute_name("first_name") # => "First name"
-
#
-
# Specify +options+ with additional translating options.
-
1
def human_attribute_name(attribute, options = {})
-
24
options = { :count => 1 }.merge!(options)
-
24
parts = attribute.to_s.split(".")
-
24
attribute = parts.pop
-
24
namespace = parts.join("/") unless parts.empty?
-
24
attributes_scope = "#{self.i18n_scope}.attributes"
-
-
24
if namespace
-
defaults = lookup_ancestors.map do |klass|
-
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
-
end
-
defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
-
else
-
24
defaults = lookup_ancestors.map do |klass|
-
24
:"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
-
end
-
end
-
-
24
defaults << :"attributes.#{attribute}"
-
24
defaults << options.delete(:default) if options[:default]
-
24
defaults << attribute.humanize
-
-
24
options[:default] = defaults
-
24
I18n.translate(defaults.shift, options)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_model/errors'
-
1
require 'active_model/validations/callbacks'
-
1
require 'active_model/validator'
-
-
1
module ActiveModel
-
-
# == Active \Model Validations
-
#
-
# Provides a full validation framework to your objects.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :first_name, :last_name
-
#
-
# validates_each :first_name, :last_name do |record, attr, value|
-
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
-
# end
-
# end
-
#
-
# Which provides you with the full standard validation stack that you
-
# know from Active Record:
-
#
-
# person = Person.new
-
# person.valid? # => true
-
# person.invalid? # => false
-
#
-
# person.first_name = 'zoolander'
-
# person.valid? # => false
-
# person.invalid? # => true
-
# person.errors.messages # => {first_name:["starts with z."]}
-
#
-
# Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
-
# method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
-
# object, so there is no need for you to do this manually.
-
1
module Validations
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
2
extend ActiveModel::Callbacks
-
2
extend ActiveModel::Translation
-
-
2
extend HelperMethods
-
2
include HelperMethods
-
-
2
attr_accessor :validation_context
-
2
define_callbacks :validate, :scope => :name
-
-
2
class_attribute :_validators
-
4
self._validators = Hash.new { |h,k| h[k] = [] }
-
end
-
-
1
module ClassMethods
-
# Validates each attribute against a block.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :first_name, :last_name
-
#
-
# validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
-
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
-
# end
-
# end
-
#
-
# Options:
-
# * <tt>:on</tt> - Specifies the context where this validation is active
-
# (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
-
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
1
def validates_each(*attr_names, &block)
-
validates_with BlockValidator, _merge_attributes(attr_names), &block
-
end
-
-
# Adds a validation method or block to the class. This is useful when
-
# overriding the +validate+ instance method becomes too unwieldy and
-
# you're looking for more descriptive declaration of your validations.
-
#
-
# This can be done with a symbol pointing to a method:
-
#
-
# class Comment
-
# include ActiveModel::Validations
-
#
-
# validate :must_be_friends
-
#
-
# def must_be_friends
-
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
-
# end
-
# end
-
#
-
# With a block which is passed with the current record to be validated:
-
#
-
# class Comment
-
# include ActiveModel::Validations
-
#
-
# validate do |comment|
-
# comment.must_be_friends
-
# end
-
#
-
# def must_be_friends
-
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
-
# end
-
# end
-
#
-
# Or with a block where self points to the current record to be validated:
-
#
-
# class Comment
-
# include ActiveModel::Validations
-
#
-
# validate do
-
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
-
# end
-
# end
-
#
-
# Options:
-
# * <tt>:on</tt> - Specifies the context where this validation is active
-
# (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
-
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
1
def validate(*args, &block)
-
5
options = args.extract_options!
-
5
if options.key?(:on)
-
options = options.dup
-
options[:if] = Array(options[:if])
-
options[:if].unshift("validation_context == :#{options[:on]}")
-
end
-
5
args << options
-
5
set_callback(:validate, *args, &block)
-
end
-
-
# List all validators that are being used to validate the model using
-
# +validates_with+ method.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validates_with MyValidator
-
# validates_with OtherValidator, on: :create
-
# validates_with StrictValidator, strict: true
-
# end
-
#
-
# Person.validators
-
# # => [
-
# # #<MyValidator:0x007fbff403e808 @options={}>,
-
# # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
-
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
-
# # ]
-
1
def validators
-
_validators.values.flatten.uniq
-
end
-
-
# List all validators that are being used to validate a specific attribute.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name , :age
-
#
-
# validates_presence_of :name
-
# validates_inclusion_of :age, in: 0..99
-
# end
-
#
-
# Person.validators_on(:name)
-
# # => [
-
# # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
-
# # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={in:0..99}>
-
# # ]
-
1
def validators_on(*attributes)
-
attributes.flat_map do |attribute|
-
_validators[attribute.to_sym]
-
end
-
end
-
-
# Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# end
-
#
-
# User.attribute_method?(:name) # => true
-
# User.attribute_method?(:age) # => false
-
1
def attribute_method?(attribute)
-
method_defined?(attribute)
-
end
-
-
# Copy validators on inheritance.
-
1
def inherited(base) #:nodoc:
-
7
dup = _validators.dup
-
7
base._validators = dup.each { |k, v| dup[k] = v.dup }
-
7
super
-
end
-
end
-
-
# Clean the +Errors+ object if instance is duped.
-
1
def initialize_dup(other) #:nodoc:
-
@errors = nil
-
super
-
end
-
-
# Returns the +Errors+ object that holds all information about attribute
-
# error messages.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name
-
# end
-
#
-
# person = Person.new
-
# person.valid? # => false
-
# person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
-
1
def errors
-
46
@errors ||= Errors.new(self)
-
end
-
-
# Runs all the specified validations and returns +true+ if no errors were
-
# added otherwise +false+.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.valid? # => false
-
# person.name = 'david'
-
# person.valid? # => true
-
#
-
# Context can optionally be supplied to define which callbacks to test
-
# against (the context is defined on the validations using <tt>:on</tt>).
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name, on: :new
-
# end
-
#
-
# person = Person.new
-
# person.valid? # => true
-
# person.valid?(:new) # => false
-
1
def valid?(context = nil)
-
current_context, self.validation_context = validation_context, context
-
errors.clear
-
run_validations!
-
ensure
-
self.validation_context = current_context
-
end
-
-
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
-
# added, +false+ otherwise.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.invalid? # => true
-
# person.name = 'david'
-
# person.invalid? # => false
-
#
-
# Context can optionally be supplied to define which callbacks to test
-
# against (the context is defined on the validations using <tt>:on</tt>).
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name, on: :new
-
# end
-
#
-
# person = Person.new
-
# person.invalid? # => false
-
# person.invalid?(:new) # => true
-
1
def invalid?(context = nil)
-
!valid?(context)
-
end
-
-
# Hook method defining how an attribute value should be retrieved. By default
-
# this is assumed to be an instance named after the attribute. Override this
-
# method in subclasses should you need to retrieve the value for a given
-
# attribute differently:
-
#
-
# class MyClass
-
# include ActiveModel::Validations
-
#
-
# def initialize(data = {})
-
# @data = data
-
# end
-
#
-
# def read_attribute_for_validation(key)
-
# @data[key]
-
# end
-
# end
-
1
alias :read_attribute_for_validation :send
-
-
1
protected
-
-
1
def run_validations! #:nodoc:
-
run_callbacks :validate
-
errors.empty?
-
end
-
end
-
end
-
-
1
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
-
12
filename = File.basename(path)
-
12
require "active_model/validations/#{filename}"
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class AcceptanceValidator < EachValidator # :nodoc:
-
1
def initialize(options)
-
super({ :allow_nil => true, :accept => "1" }.merge!(options))
-
end
-
-
1
def validate_each(record, attribute, value)
-
unless value == options[:accept]
-
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
-
end
-
end
-
-
1
def setup(klass)
-
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
-
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
-
klass.send(:attr_reader, *attr_readers)
-
klass.send(:attr_writer, *attr_writers)
-
end
-
end
-
-
1
module HelperMethods
-
# Encapsulates the pattern of wanting to validate the acceptance of a
-
# terms of service check box (or similar agreement).
-
#
-
# class Person < ActiveRecord::Base
-
# validates_acceptance_of :terms_of_service
-
# validates_acceptance_of :eula, message: 'must be abided'
-
# end
-
#
-
# If the database column does not exist, the +terms_of_service+ attribute
-
# is entirely virtual. This check is performed only if +terms_of_service+
-
# is not +nil+ and by default on save.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "must be
-
# accepted").
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default
-
# is +true+).
-
# * <tt>:accept</tt> - Specifies value that is considered accepted.
-
# The default value is a string "1", which makes it easy to relate to
-
# an HTML checkbox. This should be set to +true+ if you are validating
-
# a database column, since the attribute is typecast from "1" to +true+
-
# before validation.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_acceptance_of(*attr_names)
-
validates_with AcceptanceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require 'active_support/callbacks'
-
-
1
module ActiveModel
-
1
module Validations
-
# == Active \Model Validation Callbacks
-
#
-
# Provides an interface for any class to have +before_validation+ and
-
# +after_validation+ callbacks.
-
#
-
# First, include ActiveModel::Validations::Callbacks from the class you are
-
# creating:
-
#
-
# class MyModel
-
# include ActiveModel::Validations::Callbacks
-
#
-
# before_validation :do_stuff_before_validation
-
# after_validation :do_stuff_after_validation
-
# end
-
#
-
# Like other <tt>before_*</tt> callbacks if +before_validation+ returns
-
# +false+ then <tt>valid?</tt> will not be called.
-
1
module Callbacks
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
include ActiveSupport::Callbacks
-
1
define_callbacks :validation, :terminator => "result == false", :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name]
-
end
-
-
1
module ClassMethods
-
# Defines a callback that will get called right before validation
-
# happens.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# include ActiveModel::Validations::Callbacks
-
#
-
# attr_accessor :name
-
#
-
# validates_length_of :name, maximum: 6
-
#
-
# before_validation :remove_whitespaces
-
#
-
# private
-
#
-
# def remove_whitespaces
-
# name.strip!
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = ' bob '
-
# person.valid? # => true
-
# person.name # => "bob"
-
1
def before_validation(*args, &block)
-
options = args.last
-
if options.is_a?(Hash) && options[:on]
-
options[:if] = Array(options[:if])
-
options[:on] = Array(options[:on])
-
options[:if].unshift("#{options[:on]}.include? self.validation_context")
-
end
-
set_callback(:validation, :before, *args, &block)
-
end
-
-
# Defines a callback that will get called right after validation
-
# happens.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# include ActiveModel::Validations::Callbacks
-
#
-
# attr_accessor :name, :status
-
#
-
# validates_presence_of :name
-
#
-
# after_validation :set_status
-
#
-
# private
-
#
-
# def set_status
-
# self.status = errors.empty?
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.valid? # => false
-
# person.status # => false
-
# person.name = 'bob'
-
# person.valid? # => true
-
# person.status # => true
-
1
def after_validation(*args, &block)
-
options = args.extract_options!
-
options[:prepend] = true
-
options[:if] = Array(options[:if])
-
if options[:on]
-
options[:on] = Array(options[:on])
-
options[:if].unshift("#{options[:on]}.include? self.validation_context")
-
end
-
set_callback(:validation, :after, *(args << options), &block)
-
end
-
end
-
-
1
protected
-
-
# Overwrite run validations to include callbacks.
-
1
def run_validations! #:nodoc:
-
run_callbacks(:validation) { super }
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/range'
-
-
1
module ActiveModel
-
1
module Validations
-
1
module Clusivity #:nodoc:
-
1
ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " <<
-
"and must be supplied as the :in (or :within) option of the configuration hash"
-
-
1
def check_validity!
-
unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym)
-
raise ArgumentError, ERROR_MESSAGE
-
end
-
end
-
-
1
private
-
-
1
def include?(record, value)
-
exclusions = if delimiter.respond_to?(:call)
-
delimiter.call(record)
-
elsif delimiter.respond_to?(:to_sym)
-
record.send(delimiter)
-
else
-
delimiter
-
end
-
-
exclusions.send(inclusion_method(exclusions), value)
-
end
-
-
1
def delimiter
-
@delimiter ||= options[:in] || options[:within]
-
end
-
-
# In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
-
# range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
-
# uses the previous logic of comparing a value with the range endpoints.
-
1
def inclusion_method(enumerable)
-
enumerable.is_a?(Range) ? :cover? : :include?
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class ConfirmationValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attribute, value)
-
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
-
human_attribute_name = record.class.human_attribute_name(attribute)
-
record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name))
-
end
-
end
-
-
1
def setup(klass)
-
klass.send(:attr_accessor, *attributes.map do |attribute|
-
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
-
end.compact)
-
end
-
end
-
-
1
module HelperMethods
-
# Encapsulates the pattern of wanting to validate a password or email
-
# address field with a confirmation.
-
#
-
# Model:
-
# class Person < ActiveRecord::Base
-
# validates_confirmation_of :user_name, :password
-
# validates_confirmation_of :email_address,
-
# message: 'should match confirmation'
-
# end
-
#
-
# View:
-
# <%= password_field "person", "password" %>
-
# <%= password_field "person", "password_confirmation" %>
-
#
-
# The added +password_confirmation+ attribute is virtual; it exists only
-
# as an in-memory attribute for validating the password. To achieve this,
-
# the validation adds accessors to the model for the confirmation
-
# attribute.
-
#
-
# NOTE: This check is performed only if +password_confirmation+ is not
-
# +nil+. To require confirmation, make sure to add a presence check for
-
# the confirmation attribute:
-
#
-
# validates_presence_of :password_confirmation, if: :password_changed?
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
-
# confirmation").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_confirmation_of(*attr_names)
-
validates_with ConfirmationValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require "active_model/validations/clusivity"
-
-
1
module ActiveModel
-
-
1
module Validations
-
1
class ExclusionValidator < EachValidator # :nodoc:
-
1
include Clusivity
-
-
1
def validate_each(record, attribute, value)
-
if include?(record, value)
-
record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(:value => value))
-
end
-
end
-
end
-
-
1
module HelperMethods
-
# Validates that the value of the specified attribute is not in a
-
# particular enumerable object.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here"
-
# validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60'
-
# validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed"
-
# validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] },
-
# message: 'should not be the same as your username or first name'
-
# validates_exclusion_of :karma, in: :reserved_karmas
-
# end
-
#
-
# Configuration options:
-
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't
-
# be part of. This can be supplied as a proc, lambda or symbol which returns an
-
# enumerable. If the enumerable is a range the test is performed with
-
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
-
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
-
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
-
# reserved").
-
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the
-
# attribute is blank(default is +false+).
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_exclusion_of(*attr_names)
-
validates_with ExclusionValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class FormatValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attribute, value)
-
if options[:with]
-
regexp = option_call(record, :with)
-
record_error(record, attribute, :with, value) if value.to_s !~ regexp
-
elsif options[:without]
-
regexp = option_call(record, :without)
-
record_error(record, attribute, :without, value) if value.to_s =~ regexp
-
end
-
end
-
-
1
def check_validity!
-
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
-
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
-
end
-
-
check_options_validity(options, :with)
-
check_options_validity(options, :without)
-
end
-
-
1
private
-
-
1
def option_call(record, name)
-
option = options[name]
-
option.respond_to?(:call) ? option.call(record) : option
-
end
-
-
1
def record_error(record, attribute, name, value)
-
record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
-
end
-
-
1
def regexp_using_multiline_anchors?(regexp)
-
regexp.source.start_with?("^") ||
-
(regexp.source.end_with?("$") && !regexp.source.end_with?("\\$"))
-
end
-
-
1
def check_options_validity(options, name)
-
option = options[name]
-
if option && !option.is_a?(Regexp) && !option.respond_to?(:call)
-
raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
-
elsif option && option.is_a?(Regexp) &&
-
regexp_using_multiline_anchors?(option) && options[:multiline] != true
-
raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
-
"which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
-
":multiline => true option?"
-
end
-
end
-
end
-
-
1
module HelperMethods
-
# Validates whether the value of the specified attribute is of the correct
-
# form, going by the regular expression provided.You can require that the
-
# attribute matches the regular expression:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create
-
# end
-
#
-
# Alternatively, you can require that the specified attribute does _not_
-
# match the regular expression:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_format_of :email, without: /NOSPAM/
-
# end
-
#
-
# You can also provide a proc or lambda which will determine the regular
-
# expression that will be used to validate the attribute.
-
#
-
# class Person < ActiveRecord::Base
-
# # Admin can have number as a first letter in their screen name
-
# validates_format_of :screen_name,
-
# with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
-
# end
-
#
-
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
-
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
-
#
-
# Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
-
# the <tt>multiline: true</tt> option in case you use any of these two
-
# anchors in the provided regular expression. In most cases, you should be
-
# using <tt>\A</tt> and <tt>\z</tt>.
-
#
-
# You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
-
# In addition, both must be a regular expression or a proc or lambda, or
-
# else an exception will be raised.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
-
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the
-
# attribute is blank (default is +false+).
-
# * <tt>:with</tt> - Regular expression that if the attribute matches will
-
# result in a successful validation. This can be provided as a proc or
-
# lambda returning regular expression which will be called at runtime.
-
# * <tt>:without</tt> - Regular expression that if the attribute does not
-
# match will result in a successful validation. This can be provided as
-
# a proc or lambda returning regular expression which will be called at
-
# runtime.
-
# * <tt>:multiline</tt> - Set to true if your regular expression contains
-
# anchors that match the beginning or end of lines as opposed to the
-
# beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_format_of(*attr_names)
-
validates_with FormatValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require "active_model/validations/clusivity"
-
-
1
module ActiveModel
-
-
1
module Validations
-
1
class InclusionValidator < EachValidator # :nodoc:
-
1
include Clusivity
-
-
1
def validate_each(record, attribute, value)
-
unless include?(record, value)
-
record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(:value => value))
-
end
-
end
-
end
-
-
1
module HelperMethods
-
# Validates whether the value of the specified attribute is available in a
-
# particular enumerable object.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_inclusion_of :gender, in: %w( m f )
-
# validates_inclusion_of :age, in: 0..99
-
# validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list"
-
# validates_inclusion_of :states, in: ->(person) { STATES[person.country] }
-
# validates_inclusion_of :karma, in: :available_karmas
-
# end
-
#
-
# Configuration options:
-
# * <tt>:in</tt> - An enumerable object of available items. This can be
-
# supplied as a proc, lambda or symbol which returns an enumerable. If the
-
# enumerable is a range the test is performed with <tt>Range#cover?</tt>,
-
# otherwise with <tt>include?</tt>.
-
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
-
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
-
# not included in the list").
-
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
-
# attribute is blank (default is +false+).
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_inclusion_of(*attr_names)
-
validates_with InclusionValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
# == Active \Model Length \Validator
-
1
module Validations
-
1
class LengthValidator < EachValidator # :nodoc:
-
1
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
-
1
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
-
-
1
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
-
-
1
def initialize(options)
-
if range = (options.delete(:in) || options.delete(:within))
-
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
-
options[:minimum], options[:maximum] = range.min, range.max
-
end
-
-
super
-
end
-
-
1
def check_validity!
-
keys = CHECKS.keys & options.keys
-
-
if keys.empty?
-
raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.'
-
end
-
-
keys.each do |key|
-
value = options[key]
-
-
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
-
raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity"
-
end
-
end
-
end
-
-
1
def validate_each(record, attribute, value)
-
value = tokenize(value)
-
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
-
errors_options = options.except(*RESERVED_OPTIONS)
-
-
CHECKS.each do |key, validity_check|
-
next unless check_value = options[key]
-
next if value_length.send(validity_check, check_value)
-
-
errors_options[:count] = check_value
-
-
default_message = options[MESSAGES[key]]
-
errors_options[:message] ||= default_message if default_message
-
-
record.errors.add(attribute, MESSAGES[key], errors_options)
-
end
-
end
-
-
1
private
-
-
1
def tokenize(value)
-
if options[:tokenizer] && value.kind_of?(String)
-
options[:tokenizer].call(value)
-
end || value
-
end
-
end
-
-
1
module HelperMethods
-
-
# Validates that the specified attribute matches the length restrictions
-
# supplied. Only one option can be used at a time:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_length_of :first_name, maximum: 30
-
# validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind"
-
# validates_length_of :fax, in: 7..32, allow_nil: true
-
# validates_length_of :phone, in: 7..32, allow_blank: true
-
# validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
-
# validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
-
# validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
-
# validates_length_of :essay, minimum: 100, too_short: 'Your essay must be at least 100 words.',
-
# tokenizer: ->(str) { str.scan(/\w+/) }
-
# end
-
#
-
# Configuration options:
-
# * <tt>:minimum</tt> - The minimum size of the attribute.
-
# * <tt>:maximum</tt> - The maximum size of the attribute.
-
# * <tt>:is</tt> - The exact size of the attribute.
-
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
-
# the attribute.
-
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
-
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
-
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
-
# * <tt>:too_long</tt> - The error message if the attribute goes over the
-
# maximum (default is: "is too long (maximum is %{count} characters)").
-
# * <tt>:too_short</tt> - The error message if the attribute goes under the
-
# minimum (default is: "is too short (min is %{count} characters)").
-
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
-
# method and the attribute is the wrong size (default is: "is the wrong
-
# length (should be %{count} characters)").
-
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
-
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
-
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
-
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
-
# (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words
-
# as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
-
# which counts individual characters.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_length_of(*attr_names)
-
validates_with LengthValidator, _merge_attributes(attr_names)
-
end
-
-
1
alias_method :validates_size_of, :validates_length_of
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class NumericalityValidator < EachValidator # :nodoc:
-
1
CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
-
:equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
-
:odd => :odd?, :even => :even?, :other_than => :!= }.freeze
-
-
1
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
-
-
1
def check_validity!
-
keys = CHECKS.keys - [:odd, :even]
-
options.slice(*keys).each do |option, value|
-
next if value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
-
raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
-
end
-
end
-
-
1
def validate_each(record, attr_name, value)
-
before_type_cast = "#{attr_name}_before_type_cast"
-
-
raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast.to_sym)
-
raw_value ||= value
-
-
return if options[:allow_nil] && raw_value.nil?
-
-
unless value = parse_raw_value_as_a_number(raw_value)
-
record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
-
return
-
end
-
-
if options[:only_integer]
-
unless value = parse_raw_value_as_an_integer(raw_value)
-
record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
-
return
-
end
-
end
-
-
options.slice(*CHECKS.keys).each do |option, option_value|
-
case option
-
when :odd, :even
-
unless value.to_i.send(CHECKS[option])
-
record.errors.add(attr_name, option, filtered_options(value))
-
end
-
else
-
option_value = option_value.call(record) if option_value.is_a?(Proc)
-
option_value = record.send(option_value) if option_value.is_a?(Symbol)
-
-
unless value.send(CHECKS[option], option_value)
-
record.errors.add(attr_name, option, filtered_options(value).merge(:count => option_value))
-
end
-
end
-
end
-
end
-
-
1
protected
-
-
1
def parse_raw_value_as_a_number(raw_value)
-
case raw_value
-
when /\A0[xX]/
-
nil
-
else
-
begin
-
Kernel.Float(raw_value)
-
rescue ArgumentError, TypeError
-
nil
-
end
-
end
-
end
-
-
1
def parse_raw_value_as_an_integer(raw_value)
-
raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
-
end
-
-
1
def filtered_options(value)
-
options.except(*RESERVED_OPTIONS).merge!(:value => value)
-
end
-
end
-
-
1
module HelperMethods
-
# Validates whether the value of the specified attribute is numeric by
-
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
-
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt>
-
# (if <tt>only_integer</tt> is set to +true+).
-
#
-
# class Person < ActiveRecord::Base
-
# validates_numericality_of :value, on: :create
-
# end
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "is not a number").
-
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
-
# integer, e.g. an integral value (default is +false+).
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
-
# +false+). Notice that for fixnum and float columns empty strings are
-
# converted to +nil+.
-
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
-
# supplied value.
-
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
-
# greater than or equal the supplied value.
-
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
-
# value.
-
# * <tt>:less_than</tt> - Specifies the value must be less than the
-
# supplied value.
-
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
-
# than or equal the supplied value.
-
# * <tt>:other_than</tt> - Specifies the value must be other than the
-
# supplied value.
-
# * <tt>:odd</tt> - Specifies the value must be an odd number.
-
# * <tt>:even</tt> - Specifies the value must be an even number.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+ .
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
#
-
# The following checks can also be supplied with a proc or a symbol which
-
# corresponds to a method:
-
#
-
# * <tt>:greater_than</tt>
-
# * <tt>:greater_than_or_equal_to</tt>
-
# * <tt>:equal_to</tt>
-
# * <tt>:less_than</tt>
-
# * <tt>:less_than_or_equal_to</tt>
-
#
-
# For example:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_numericality_of :width, less_than: ->(person) { person.height }
-
# validates_numericality_of :width, greater_than: :minimum_weight
-
# end
-
1
def validates_numericality_of(*attr_names)
-
validates_with NumericalityValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
-
1
module ActiveModel
-
-
1
module Validations
-
1
class PresenceValidator < EachValidator # :nodoc:
-
1
def validate(record)
-
record.errors.add_on_blank(attributes, options)
-
end
-
end
-
-
1
module HelperMethods
-
# Validates that the specified attributes are not blank (as defined by
-
# Object#blank?). Happens by default on save.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_presence_of :first_name
-
# end
-
#
-
# The first_name attribute must be in the object and it cannot be blank.
-
#
-
# If you want to validate the presence of a boolean field (where the real
-
# values are +true+ and +false+), you will want to use
-
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
-
#
-
# This is due to the way Object#blank? handles boolean values:
-
# <tt>false.blank? # => true</tt>.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_presence_of(*attr_names)
-
validates_with PresenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/slice'
-
-
1
module ActiveModel
-
1
module Validations
-
1
module ClassMethods
-
# This method is a shortcut to all default validators and any custom
-
# validator classes ending in 'Validator'. Note that Rails default
-
# validators can be overridden inside specific classes by creating
-
# custom validator classes in their place such as PresenceValidator.
-
#
-
# Examples of using the default rails validators:
-
#
-
# validates :terms, acceptance: true
-
# validates :password, confirmation: true
-
# validates :username, exclusion: { in: %w(admin superuser) }
-
# validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, on: :create }
-
# validates :age, inclusion: { in: 0..9 }
-
# validates :first_name, length: { maximum: 30 }
-
# validates :age, numericality: true
-
# validates :username, presence: true
-
# validates :username, uniqueness: true
-
#
-
# The power of the +validates+ method comes when using custom validators
-
# and default validators in one call for a given attribute.
-
#
-
# class EmailValidator < ActiveModel::EachValidator
-
# def validate_each(record, attribute, value)
-
# record.errors.add attribute, (options[:message] || "is not an email") unless
-
# value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
-
# end
-
# end
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# attr_accessor :name, :email
-
#
-
# validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
-
# validates :email, presence: true, email: true
-
# end
-
#
-
# Validator classes may also exist within the class being validated
-
# allowing custom modules of validators to be included as needed.
-
#
-
# class Film
-
# include ActiveModel::Validations
-
#
-
# class TitleValidator < ActiveModel::EachValidator
-
# def validate_each(record, attribute, value)
-
# record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i
-
# end
-
# end
-
#
-
# validates :name, title: true
-
# end
-
#
-
# Additionally validator classes may be in another namespace and still
-
# used within any class.
-
#
-
# validates :name, :'film/title' => true
-
#
-
# The validators hash can also handle regular expressions, ranges, arrays
-
# and strings in shortcut form.
-
#
-
# validates :email, format: /@/
-
# validates :gender, inclusion: %w(male female)
-
# validates :password, length: 6..20
-
#
-
# When using shortcut form, ranges and arrays are passed to your
-
# validator's initializer as <tt>options[:in]</tt> while other types
-
# including regular expressions and strings are passed as <tt>options[:with]</tt>.
-
#
-
# There is also a list of options that could be used along with validators:
-
#
-
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
-
# validation contexts by default (+nil+), other options are <tt>:create</tt>
-
# and <tt>:update</tt>.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or
-
# +false+ value.
-
# * <tt>:strict</tt> - if the <tt>:strict</tt> option is set to true
-
# will raise ActiveModel::StrictValidationFailed instead of adding the error.
-
# <tt>:strict</tt> option can also be set to any other exception.
-
#
-
# Example:
-
#
-
# validates :password, presence: true, confirmation: true, if: :password_required?
-
# validates :token, uniqueness: true, strict: TokenGenerationException
-
#
-
#
-
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
-
# and +:message+ can be given to one specific validator, as a hash:
-
#
-
# validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true
-
1
def validates(*attributes)
-
defaults = attributes.extract_options!.dup
-
validations = defaults.slice!(*_validates_default_keys)
-
-
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
-
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
-
-
defaults[:attributes] = attributes
-
-
validations.each do |key, options|
-
next unless options
-
key = "#{key.to_s.camelize}Validator"
-
-
begin
-
validator = key.include?('::') ? key.constantize : const_get(key)
-
rescue NameError
-
raise ArgumentError, "Unknown validator: '#{key}'"
-
end
-
-
validates_with(validator, defaults.merge(_parse_validates_options(options)))
-
end
-
end
-
-
# This method is used to define validations that cannot be corrected by end
-
# users and are considered exceptional. So each validator defined with bang
-
# or <tt>:strict</tt> option set to <tt>true</tt> will always raise
-
# <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
-
# when validation fails. See <tt>validates</tt> for more information about
-
# the validation itself.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates! :name, presence: true
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.valid?
-
# # => ActiveModel::StrictValidationFailed: Name can't be blank
-
1
def validates!(*attributes)
-
options = attributes.extract_options!
-
options[:strict] = true
-
validates(*(attributes << options))
-
end
-
-
1
protected
-
-
# When creating custom validators, it might be useful to be able to specify
-
# additional default keys. This can be done by overwriting this method.
-
1
def _validates_default_keys # :nodoc:
-
[:if, :unless, :on, :allow_blank, :allow_nil , :strict]
-
end
-
-
1
def _parse_validates_options(options) # :nodoc:
-
case options
-
when TrueClass
-
{}
-
when Hash
-
options
-
when Range, Array
-
{ :in => options }
-
else
-
{ :with => options }
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
1
module Validations
-
1
module HelperMethods
-
1
private
-
1
def _merge_attributes(attr_names)
-
2
options = attr_names.extract_options!.symbolize_keys
-
2
attr_names.flatten!
-
2
options[:attributes] = attr_names
-
2
options
-
end
-
end
-
-
1
class WithValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attr, val)
-
method_name = options[:with]
-
-
if record.method(method_name).arity == 0
-
record.send method_name
-
else
-
record.send method_name, attr
-
end
-
end
-
end
-
-
1
module ClassMethods
-
# Passes the record off to the class or classes specified and allows them
-
# to add errors based on more complex conditions.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# if some_complex_logic
-
# record.errors.add :base, 'This record is invalid'
-
# end
-
# end
-
#
-
# private
-
# def some_complex_logic
-
# # ...
-
# end
-
# end
-
#
-
# You may also pass it multiple classes, like so:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator, MyOtherValidator, on: :create
-
# end
-
#
-
# Configuration options:
-
# * <tt>:on</tt> - Specifies when this validation is active
-
# (<tt>:create</tt> or <tt>:update</tt>.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>).
-
# The method, proc or string should return or evaluate to a +true+ or
-
# +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur
-
# (e.g. <tt>unless: :skip_validation</tt>, or
-
# <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>).
-
# The method, proc or string should return or evaluate to a +true+ or
-
# +false+ value.
-
# * <tt>:strict</tt> - Specifies whether validation should be strict.
-
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
-
#
-
# If you pass any additional configuration options, they will be passed
-
# to the class and available as +options+:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator, my_custom_key: 'my custom value'
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# options[:my_custom_key] # => "my custom value"
-
# end
-
# end
-
1
def validates_with(*args, &block)
-
2
options = args.extract_options!
-
2
args.each do |klass|
-
2
validator = klass.new(options, &block)
-
2
validator.setup(self) if validator.respond_to?(:setup)
-
-
2
if validator.respond_to?(:attributes) && !validator.attributes.empty?
-
2
validator.attributes.each do |attribute|
-
2
_validators[attribute.to_sym] << validator
-
end
-
else
-
_validators[nil] << validator
-
end
-
-
2
validate(validator, options)
-
end
-
end
-
end
-
-
# Passes the record off to the class or classes specified and allows them
-
# to add errors based on more complex conditions.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validate :instance_validations
-
#
-
# def instance_validations
-
# validates_with MyValidator
-
# end
-
# end
-
#
-
# Please consult the class method documentation for more information on
-
# creating your own validator.
-
#
-
# You may also pass it multiple classes, like so:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validate :instance_validations, on: :create
-
#
-
# def instance_validations
-
# validates_with MyValidator, MyOtherValidator
-
# end
-
# end
-
#
-
# Standard configuration options (<tt>:on</tt>, <tt>:if</tt> and
-
# <tt>:unless</tt>), which are available on the class version of
-
# +validates_with+, should instead be placed on the +validates+ method
-
# as these are applied and tested in the callback.
-
#
-
# If you pass any additional configuration options, they will be passed
-
# to the class and available as +options+, please refer to the
-
# class version of this method for more information.
-
1
def validates_with(*args, &block)
-
options = args.extract_options!
-
args.each do |klass|
-
validator = klass.new(options, &block)
-
validator.validate(self)
-
end
-
end
-
end
-
end
-
1
require "active_support/core_ext/module/anonymous"
-
-
1
module ActiveModel
-
-
# == Active \Model \Validator
-
#
-
# A simple base class that can be used along with
-
# ActiveModel::Validations::ClassMethods.validates_with
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# if some_complex_logic
-
# record.errors[:base] = "This record is invalid"
-
# end
-
# end
-
#
-
# private
-
# def some_complex_logic
-
# # ...
-
# end
-
# end
-
#
-
# Any class that inherits from ActiveModel::Validator must implement a method
-
# called +validate+ which accepts a +record+.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# record # => The person instance being validated
-
# options # => Any non-standard options passed to validates_with
-
# end
-
# end
-
#
-
# To cause a validation error, you must add to the +record+'s errors directly
-
# from within the validators message.
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# record.errors.add :base, "This is some custom error message"
-
# record.errors.add :first_name, "This is some complex validation"
-
# # etc...
-
# end
-
# end
-
#
-
# To add behavior to the initialize method, use the following signature:
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def initialize(options)
-
# super
-
# @my_custom_field = options[:field_name] || :first_name
-
# end
-
# end
-
#
-
# The easiest way to add custom validators for validating individual attributes
-
# is with the convenient <tt>ActiveModel::EachValidator</tt>.
-
#
-
# class TitleValidator < ActiveModel::EachValidator
-
# def validate_each(record, attribute, value)
-
# record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value)
-
# end
-
# end
-
#
-
# This can now be used in combination with the +validates+ method
-
# (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this).
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# attr_accessor :title
-
#
-
# validates :title, presence: true
-
# end
-
#
-
# Validator may also define a +setup+ instance method which will get called
-
# with the class that using that validator as its argument. This can be
-
# useful when there are prerequisites such as an +attr_accessor+ being present.
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def setup(klass)
-
# klass.send :attr_accessor, :custom_attribute
-
# end
-
# end
-
#
-
# This setup method is only called when used with validation macros or the
-
# class level <tt>validates_with</tt> method.
-
1
class Validator
-
1
attr_reader :options
-
-
# Returns the kind of the validator.
-
#
-
# PresenceValidator.kind # => :presence
-
# UniquenessValidator.kind # => :uniqueness
-
1
def self.kind
-
@kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
-
end
-
-
# Accepts options that will be made available through the +options+ reader.
-
1
def initialize(options)
-
2
@options = options.freeze
-
end
-
-
# Return the kind for this validator.
-
#
-
# PresenceValidator.new.kind # => :presence
-
# UniquenessValidator.new.kind # => :uniqueness
-
1
def kind
-
self.class.kind
-
end
-
-
# Override this method in subclasses with validation logic, adding errors
-
# to the records +errors+ array where necessary.
-
1
def validate(record)
-
raise NotImplementedError, "Subclasses must implement a validate(record) method."
-
end
-
end
-
-
# +EachValidator+ is a validator which iterates through the attributes given
-
# in the options hash invoking the <tt>validate_each</tt> method passing in the
-
# record, attribute and value.
-
#
-
# All Active Model validations are built on top of this validator.
-
1
class EachValidator < Validator #:nodoc:
-
1
attr_reader :attributes
-
-
# Returns a new validator instance. All options will be available via the
-
# +options+ reader, however the <tt>:attributes</tt> option will be removed
-
# and instead be made available through the +attributes+ reader.
-
1
def initialize(options)
-
2
@attributes = Array(options.delete(:attributes))
-
2
raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
-
2
super
-
2
check_validity!
-
end
-
-
# Performs validation on the supplied record. By default this will call
-
# +validates_each+ to determine validity therefore subclasses should
-
# override +validates_each+ with validation logic.
-
1
def validate(record)
-
attributes.each do |attribute|
-
value = record.read_attribute_for_validation(attribute)
-
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
-
validate_each(record, attribute, value)
-
end
-
end
-
-
# Override this method in subclasses with the validation logic, adding
-
# errors to the records +errors+ array where necessary.
-
1
def validate_each(record, attribute, value)
-
raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
-
end
-
-
# Hook method that gets called by the initializer allowing verification
-
# that the arguments supplied are valid. You could for example raise an
-
# +ArgumentError+ when invalid options are supplied.
-
1
def check_validity!
-
end
-
end
-
-
# +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
-
# and call this block for each attribute being validated. +validates_each+ uses this validator.
-
1
class BlockValidator < EachValidator #:nodoc:
-
1
def initialize(options, &block)
-
@block = block
-
super
-
end
-
-
1
private
-
-
1
def validate_each(record, attribute, value)
-
@block.call(record, attribute, value)
-
end
-
end
-
end
-
1
module ActiveModel
-
1
module VERSION #:nodoc:
-
1
MAJOR = 4
-
1
MINOR = 0
-
1
TINY = 0
-
1
PRE = "beta"
-
-
1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
-
end
-
end
-
#--
-
# Copyright (c) 2004-2012 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
1
require 'simplecov'
-
1
SimpleCov.start
-
1
puts "Started SimpleCov active_record.rb"
-
1
require 'active_support'
-
1
require 'active_support/rails'
-
1
require 'active_model'
-
1
require 'arel'
-
1
require 'active_record/deprecated_finders'
-
-
1
require 'active_record/version'
-
-
1
module ActiveRecord
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Base
-
1
autoload :Callbacks
-
1
autoload :Core
-
1
autoload :CounterCache
-
1
autoload :ConnectionHandling
-
1
autoload :DynamicMatchers
-
1
autoload :Explain
-
1
autoload :Inheritance
-
1
autoload :Integration
-
1
autoload :Migration
-
1
autoload :Migrator, 'active_record/migration'
-
1
autoload :ModelSchema
-
1
autoload :NestedAttributes
-
1
autoload :Observer
-
1
autoload :Persistence
-
1
autoload :QueryCache
-
1
autoload :Querying
-
1
autoload :ReadonlyAttributes
-
1
autoload :Reflection
-
1
autoload :Sanitization
-
1
autoload :Schema
-
1
autoload :SchemaDumper
-
1
autoload :SchemaMigration
-
1
autoload :Scoping
-
1
autoload :Serialization
-
1
autoload :Store
-
1
autoload :Timestamp
-
1
autoload :Transactions
-
1
autoload :Translation
-
1
autoload :Validations
-
-
1
eager_autoload do
-
1
autoload :ActiveRecordError, 'active_record/errors'
-
1
autoload :ConnectionNotEstablished, 'active_record/errors'
-
1
autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
-
-
1
autoload :Aggregations
-
1
autoload :Associations
-
1
autoload :AttributeMethods
-
1
autoload :AttributeAssignment
-
1
autoload :AutosaveAssociation
-
-
1
autoload :Relation
-
1
autoload :NullRelation
-
-
1
autoload_under 'relation' do
-
1
autoload :QueryMethods
-
1
autoload :FinderMethods
-
1
autoload :Calculations
-
1
autoload :PredicateBuilder
-
1
autoload :SpawnMethods
-
1
autoload :Batches
-
1
autoload :Delegation
-
end
-
-
1
autoload :Result
-
end
-
-
1
module Coders
-
1
autoload :YAMLColumn, 'active_record/coders/yaml_column'
-
end
-
-
1
module AttributeMethods
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :BeforeTypeCast
-
1
autoload :Dirty
-
1
autoload :PrimaryKey
-
1
autoload :Query
-
1
autoload :Read
-
1
autoload :TimeZoneConversion
-
1
autoload :Write
-
1
autoload :Serialization
-
end
-
end
-
-
1
module Locking
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :Optimistic
-
1
autoload :Pessimistic
-
end
-
end
-
-
1
module ConnectionAdapters
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :AbstractAdapter
-
1
autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool"
-
end
-
end
-
-
1
module Scoping
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :Named
-
1
autoload :Default
-
end
-
end
-
-
1
module Tasks
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :DatabaseTasks
-
1
autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks'
-
1
autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
-
1
autoload :PostgreSQLDatabaseTasks,
-
'active_record/tasks/postgresql_database_tasks'
-
end
-
-
1
autoload :TestCase
-
1
autoload :TestFixtures, 'active_record/fixtures'
-
-
1
def self.eager_load!
-
super
-
ActiveRecord::Locking.eager_load!
-
ActiveRecord::Scoping.eager_load!
-
ActiveRecord::Associations.eager_load!
-
ActiveRecord::AttributeMethods.eager_load!
-
ActiveRecord::ConnectionAdapters.eager_load!
-
end
-
end
-
-
1
ActiveSupport.on_load(:active_record) do
-
1
Arel::Table.engine = self
-
end
-
-
1
ActiveSupport.on_load(:i18n) do
-
1
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
-
end
-
1
module ActiveRecord
-
# = Active Record Aggregations
-
1
module Aggregations # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
def clear_aggregation_cache #:nodoc:
-
@aggregation_cache.clear if persisted?
-
end
-
-
# Active Record implements aggregation through a macro-like class method called +composed_of+
-
# for representing attributes as value objects. It expresses relationships like "Account [is]
-
# composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
-
# to the macro adds a description of how the value objects are created from the attributes of
-
# the entity object (when the entity is initialized either as a new object or from finding an
-
# existing object) and how it can be turned back into attributes (when the entity is saved to
-
# the database).
-
#
-
# class Customer < ActiveRecord::Base
-
# composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
-
# composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
-
# end
-
#
-
# The customer class now has the following methods to manipulate the value objects:
-
# * <tt>Customer#balance, Customer#balance=(money)</tt>
-
# * <tt>Customer#address, Customer#address=(address)</tt>
-
#
-
# These methods will operate with value objects like the ones described below:
-
#
-
# class Money
-
# include Comparable
-
# attr_reader :amount, :currency
-
# EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
-
#
-
# def initialize(amount, currency = "USD")
-
# @amount, @currency = amount, currency
-
# end
-
#
-
# def exchange_to(other_currency)
-
# exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
-
# Money.new(exchanged_amount, other_currency)
-
# end
-
#
-
# def ==(other_money)
-
# amount == other_money.amount && currency == other_money.currency
-
# end
-
#
-
# def <=>(other_money)
-
# if currency == other_money.currency
-
# amount <=> other_money.amount
-
# else
-
# amount <=> other_money.exchange_to(currency).amount
-
# end
-
# end
-
# end
-
#
-
# class Address
-
# attr_reader :street, :city
-
# def initialize(street, city)
-
# @street, @city = street, city
-
# end
-
#
-
# def close_to?(other_address)
-
# city == other_address.city
-
# end
-
#
-
# def ==(other_address)
-
# city == other_address.city && street == other_address.street
-
# end
-
# end
-
#
-
# Now it's possible to access attributes from the database through the value objects instead. If
-
# you choose to name the composition the same as the attribute's name, it will be the only way to
-
# access that attribute. That's the case with our +balance+ attribute. You interact with the value
-
# objects just like you would with any other attribute:
-
#
-
# customer.balance = Money.new(20) # sets the Money value object and the attribute
-
# customer.balance # => Money value object
-
# customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
-
# customer.balance > Money.new(10) # => true
-
# customer.balance == Money.new(20) # => true
-
# customer.balance < Money.new(5) # => false
-
#
-
# Value objects can also be composed of multiple attributes, such as the case of Address. The order
-
# of the mappings will determine the order of the parameters.
-
#
-
# customer.address_street = "Hyancintvej"
-
# customer.address_city = "Copenhagen"
-
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
-
#
-
# customer.address_street = "Vesterbrogade"
-
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
-
# customer.clear_aggregation_cache
-
# customer.address # => Address.new("Vesterbrogade", "Copenhagen")
-
#
-
# customer.address = Address.new("May Street", "Chicago")
-
# customer.address_street # => "May Street"
-
# customer.address_city # => "Chicago"
-
#
-
# == Writing value objects
-
#
-
# Value objects are immutable and interchangeable objects that represent a given value, such as
-
# a Money object representing $5. Two Money objects both representing $5 should be equal (through
-
# methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
-
# unlike entity objects where equality is determined by identity. An entity class such as Customer can
-
# easily have two different objects that both have an address on Hyancintvej. Entity identity is
-
# determined by object or relational unique identifiers (such as primary keys). Normal
-
# ActiveRecord::Base classes are entity objects.
-
#
-
# It's also important to treat the value objects as immutable. Don't allow the Money object to have
-
# its amount changed after creation. Create a new Money object with the new value instead. The
-
# Money#exchange_to method is an example of this. It returns a new value object instead of changing
-
# its own values. Active Record won't persist value objects that have been changed through means
-
# other than the writer method.
-
#
-
# The immutable requirement is enforced by Active Record by freezing any object assigned as a value
-
# object. Attempting to change it afterwards will result in a ActiveSupport::FrozenObjectError.
-
#
-
# Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
-
# keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
-
#
-
# == Custom constructors and converters
-
#
-
# By default value objects are initialized by calling the <tt>new</tt> constructor of the value
-
# class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
-
# option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
-
# a custom constructor to be specified.
-
#
-
# When a new value is assigned to the value object, the default assumption is that the new value
-
# is an instance of the value class. Specifying a custom converter allows the new value to be automatically
-
# converted to an instance of value class if necessary.
-
#
-
# For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
-
# should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
-
# for the value class is called +create+ and it expects a CIDR address string as a parameter. New
-
# values can be assigned to the value object using either another NetAddr::CIDR object, a string
-
# or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
-
# these requirements:
-
#
-
# class NetworkResource < ActiveRecord::Base
-
# composed_of :cidr,
-
# :class_name => 'NetAddr::CIDR',
-
# :mapping => [ %w(network_address network), %w(cidr_range bits) ],
-
# :allow_nil => true,
-
# :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
-
# :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
-
# end
-
#
-
# # This calls the :constructor
-
# network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
-
#
-
# # These assignments will both use the :converter
-
# network_resource.cidr = [ '192.168.2.1', 8 ]
-
# network_resource.cidr = '192.168.0.1/24'
-
#
-
# # This assignment won't use the :converter as the value is already an instance of the value class
-
# network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
-
#
-
# # Saving and then reloading will use the :constructor on reload
-
# network_resource.save
-
# network_resource.reload
-
#
-
# == Finding records by a value object
-
#
-
# Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
-
# by specifying an instance of the value object in the conditions hash. The following example
-
# finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
-
#
-
# Customer.where(:balance => Money.new(20, "USD")).all
-
#
-
1
module ClassMethods
-
# Adds reader and writer methods for manipulating a value object:
-
# <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
-
#
-
# Options are:
-
# * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
-
# can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
-
# to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
-
# with this option.
-
# * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
-
# object. Each mapping is represented as an array where the first item is the name of the
-
# entity attribute and the second item is the name of the attribute in the value object. The
-
# order in which mappings are defined determines the order in which attributes are sent to the
-
# value class constructor.
-
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
-
# attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
-
# mapped attributes.
-
# This defaults to +false+.
-
# * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
-
# is called to initialize the value object. The constructor is passed all of the mapped attributes,
-
# in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
-
# to instantiate a <tt>:class_name</tt> object.
-
# The default is <tt>:new</tt>.
-
# * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
-
# or a Proc that is called when a new value is assigned to the value object. The converter is
-
# passed the single value that is used in the assignment and is only called if the new value is
-
# not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
-
# can return nil to skip the assignment.
-
#
-
# Option examples:
-
# composed_of :temperature, :mapping => %w(reading celsius)
-
# composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
-
# :converter => Proc.new { |balance| balance.to_money }
-
# composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
-
# composed_of :gps_location
-
# composed_of :gps_location, :allow_nil => true
-
# composed_of :ip_address,
-
# :class_name => 'IPAddr',
-
# :mapping => %w(ip to_i),
-
# :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
-
# :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
-
#
-
1
def composed_of(part_id, options = {})
-
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
-
-
name = part_id.id2name
-
class_name = options[:class_name] || name.camelize
-
mapping = options[:mapping] || [ name, name ]
-
mapping = [ mapping ] unless mapping.first.is_a?(Array)
-
allow_nil = options[:allow_nil] || false
-
constructor = options[:constructor] || :new
-
converter = options[:converter]
-
-
reader_method(name, class_name, mapping, allow_nil, constructor)
-
writer_method(name, class_name, mapping, allow_nil, converter)
-
-
create_reflection(:composed_of, part_id, nil, options, self)
-
end
-
-
1
private
-
1
def reader_method(name, class_name, mapping, allow_nil, constructor)
-
define_method(name) do
-
if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
-
attrs = mapping.collect {|pair| read_attribute(pair.first)}
-
object = constructor.respond_to?(:call) ?
-
constructor.call(*attrs) :
-
class_name.constantize.send(constructor, *attrs)
-
@aggregation_cache[name] = object
-
end
-
@aggregation_cache[name]
-
end
-
end
-
-
1
def writer_method(name, class_name, mapping, allow_nil, converter)
-
define_method("#{name}=") do |part|
-
klass = class_name.constantize
-
unless part.is_a?(klass) || converter.nil? || part.nil?
-
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
-
end
-
-
if part.nil? && allow_nil
-
mapping.each { |pair| self[pair.first] = nil }
-
@aggregation_cache[name] = nil
-
else
-
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
-
@aggregation_cache[name] = part.freeze
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/enumerable'
-
1
require 'active_support/core_ext/string/conversions'
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'active_support/dependencies/autoload'
-
1
require 'active_support/concern'
-
1
require 'active_record/errors'
-
-
1
module ActiveRecord
-
1
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
-
1
def initialize(reflection, associated_class = nil)
-
super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
-
end
-
end
-
-
1
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection)
-
super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
-
end
-
end
-
-
1
class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection, source_reflection)
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
-
end
-
end
-
-
1
class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection)
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
-
end
-
end
-
-
1
class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection, source_reflection)
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
-
end
-
end
-
-
1
class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection, through_reflection)
-
super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
-
end
-
end
-
-
1
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
through_reflection = reflection.through_reflection
-
source_reflection_names = reflection.source_reflection_names
-
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
-
super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
-
end
-
end
-
-
1
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
-
end
-
end
-
-
1
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
-
end
-
end
-
-
1
class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
-
end
-
end
-
-
1
class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
-
end
-
end
-
-
1
class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.")
-
end
-
end
-
-
1
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
-
end
-
end
-
-
1
class ReadOnlyAssociation < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
-
end
-
end
-
-
# This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
-
# (has_many, has_one) when there is at least 1 child associated instance.
-
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
-
1
class DeleteRestrictionError < ActiveRecordError #:nodoc:
-
1
def initialize(name)
-
super("Cannot delete record because of dependent #{name}")
-
end
-
end
-
-
# See ActiveRecord::Associations::ClassMethods for documentation.
-
1
module Associations # :nodoc:
-
1
extend ActiveSupport::Autoload
-
1
extend ActiveSupport::Concern
-
-
# These classes will be loaded when associations are created.
-
# So there is no need to eager load them.
-
1
autoload :Association, 'active_record/associations/association'
-
1
autoload :SingularAssociation, 'active_record/associations/singular_association'
-
1
autoload :CollectionAssociation, 'active_record/associations/collection_association'
-
1
autoload :CollectionProxy, 'active_record/associations/collection_proxy'
-
-
1
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
-
1
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
-
1
autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
-
1
autoload :HasManyAssociation, 'active_record/associations/has_many_association'
-
1
autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
-
1
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
-
1
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
-
1
autoload :ThroughAssociation, 'active_record/associations/through_association'
-
-
1
module Builder #:nodoc:
-
1
autoload :Association, 'active_record/associations/builder/association'
-
1
autoload :SingularAssociation, 'active_record/associations/builder/singular_association'
-
1
autoload :CollectionAssociation, 'active_record/associations/builder/collection_association'
-
-
1
autoload :BelongsTo, 'active_record/associations/builder/belongs_to'
-
1
autoload :HasOne, 'active_record/associations/builder/has_one'
-
1
autoload :HasMany, 'active_record/associations/builder/has_many'
-
1
autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many'
-
end
-
-
1
eager_autoload do
-
1
autoload :Preloader, 'active_record/associations/preloader'
-
1
autoload :JoinDependency, 'active_record/associations/join_dependency'
-
1
autoload :AssociationScope, 'active_record/associations/association_scope'
-
1
autoload :AliasTracker, 'active_record/associations/alias_tracker'
-
1
autoload :JoinHelper, 'active_record/associations/join_helper'
-
end
-
-
# Clears out the association cache.
-
1
def clear_association_cache #:nodoc:
-
@association_cache.clear if persisted?
-
end
-
-
# :nodoc:
-
1
attr_reader :association_cache
-
-
# Returns the association instance for the given name, instantiating it if it doesn't already exist
-
1
def association(name) #:nodoc:
-
association = association_instance_get(name)
-
-
if association.nil?
-
reflection = self.class.reflect_on_association(name)
-
association = reflection.association_class.new(self, reflection)
-
association_instance_set(name, association)
-
end
-
-
association
-
end
-
-
1
private
-
# Returns the specified association instance if it responds to :loaded?, nil otherwise.
-
1
def association_instance_get(name)
-
@association_cache[name.to_sym]
-
end
-
-
# Set the specified association instance.
-
1
def association_instance_set(name, association)
-
@association_cache[name] = association
-
end
-
-
# Associations are a set of macro-like class methods for tying objects together through
-
# foreign keys. They express relationships like "Project has one Project Manager"
-
# or "Project belongs to a Portfolio". Each macro adds a number of methods to the
-
# class which are specialized according to the collection or association symbol and the
-
# options hash. It works much the same way as Ruby's own <tt>attr*</tt>
-
# methods.
-
#
-
# class Project < ActiveRecord::Base
-
# belongs_to :portfolio
-
# has_one :project_manager
-
# has_many :milestones
-
# has_and_belongs_to_many :categories
-
# end
-
#
-
# The project class now has the following methods (and more) to ease the traversal and
-
# manipulation of its relationships:
-
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
-
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
-
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
-
# <tt>Project#milestones.delete(milestone), Project#milestones.destroy(mileston), Project#milestones.find(milestone_id),</tt>
-
# <tt>Project#milestones.all(options), Project#milestones.build, Project#milestones.create</tt>
-
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
-
# <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt>
-
#
-
# === A word of warning
-
#
-
# Don't create associations that have the same name as instance methods of
-
# <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
-
# its model, it will override the inherited method and break things.
-
# For instance, +attributes+ and +connection+ would be bad choices for association names.
-
#
-
# == Auto-generated methods
-
#
-
# === Singular associations (one-to-one)
-
# | | belongs_to |
-
# generated methods | belongs_to | :polymorphic | has_one
-
# ----------------------------------+------------+--------------+---------
-
# other | X | X | X
-
# other=(other) | X | X | X
-
# build_other(attributes={}) | X | | X
-
# create_other(attributes={}) | X | | X
-
# create_other!(attributes={}) | X | | X
-
#
-
# ===Collection associations (one-to-many / many-to-many)
-
# | | | has_many
-
# generated methods | habtm | has_many | :through
-
# ----------------------------------+-------+----------+----------
-
# others | X | X | X
-
# others=(other,other,...) | X | X | X
-
# other_ids | X | X | X
-
# other_ids=(id,id,...) | X | X | X
-
# others<< | X | X | X
-
# others.push | X | X | X
-
# others.concat | X | X | X
-
# others.build(attributes={}) | X | X | X
-
# others.create(attributes={}) | X | X | X
-
# others.create!(attributes={}) | X | X | X
-
# others.size | X | X | X
-
# others.length | X | X | X
-
# others.count | X | X | X
-
# others.sum(args*,&block) | X | X | X
-
# others.empty? | X | X | X
-
# others.clear | X | X | X
-
# others.delete(other,other,...) | X | X | X
-
# others.delete_all | X | X | X
-
# others.destroy(other,other,...) | X | X | X
-
# others.destroy_all | X | X | X
-
# others.find(*args) | X | X | X
-
# others.exists? | X | X | X
-
# others.uniq | X | X | X
-
# others.reset | X | X | X
-
#
-
# === Overriding generated methods
-
#
-
# Association methods are generated in a module that is included into the model class,
-
# which allows you to easily override with your own methods and call the original
-
# generated method with +super+. For example:
-
#
-
# class Car < ActiveRecord::Base
-
# belongs_to :owner
-
# belongs_to :old_owner
-
# def owner=(new_owner)
-
# self.old_owner = self.owner
-
# super
-
# end
-
# end
-
#
-
# If your model class is <tt>Project</tt>, the module is
-
# named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods module is
-
# included in the model class immediately after the (anonymous) generated attributes methods
-
# module, meaning an association will override the methods for an attribute with the same name.
-
#
-
# == Cardinality and associations
-
#
-
# Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
-
# relationships between models. Each model uses an association to describe its role in
-
# the relation. The +belongs_to+ association is always used in the model that has
-
# the foreign key.
-
#
-
# === One-to-one
-
#
-
# Use +has_one+ in the base, and +belongs_to+ in the associated model.
-
#
-
# class Employee < ActiveRecord::Base
-
# has_one :office
-
# end
-
# class Office < ActiveRecord::Base
-
# belongs_to :employee # foreign key - employee_id
-
# end
-
#
-
# === One-to-many
-
#
-
# Use +has_many+ in the base, and +belongs_to+ in the associated model.
-
#
-
# class Manager < ActiveRecord::Base
-
# has_many :employees
-
# end
-
# class Employee < ActiveRecord::Base
-
# belongs_to :manager # foreign key - manager_id
-
# end
-
#
-
# === Many-to-many
-
#
-
# There are two ways to build a many-to-many relationship.
-
#
-
# The first way uses a +has_many+ association with the <tt>:through</tt> option and a join model, so
-
# there are two stages of associations.
-
#
-
# class Assignment < ActiveRecord::Base
-
# belongs_to :programmer # foreign key - programmer_id
-
# belongs_to :project # foreign key - project_id
-
# end
-
# class Programmer < ActiveRecord::Base
-
# has_many :assignments
-
# has_many :projects, :through => :assignments
-
# end
-
# class Project < ActiveRecord::Base
-
# has_many :assignments
-
# has_many :programmers, :through => :assignments
-
# end
-
#
-
# For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table
-
# that has no corresponding model or primary key.
-
#
-
# class Programmer < ActiveRecord::Base
-
# has_and_belongs_to_many :projects # foreign keys in the join table
-
# end
-
# class Project < ActiveRecord::Base
-
# has_and_belongs_to_many :programmers # foreign keys in the join table
-
# end
-
#
-
# Choosing which way to build a many-to-many relationship is not always simple.
-
# If you need to work with the relationship model as its own entity,
-
# use <tt>has_many :through</tt>. Use +has_and_belongs_to_many+ when working with legacy schemas or when
-
# you never work directly with the relationship itself.
-
#
-
# == Is it a +belongs_to+ or +has_one+ association?
-
#
-
# Both express a 1-1 relationship. The difference is mostly where to place the foreign
-
# key, which goes on the table for the class declaring the +belongs_to+ relationship.
-
#
-
# class User < ActiveRecord::Base
-
# # I reference an account.
-
# belongs_to :account
-
# end
-
#
-
# class Account < ActiveRecord::Base
-
# # One user references me.
-
# has_one :user
-
# end
-
#
-
# The tables for these classes could look something like:
-
#
-
# CREATE TABLE users (
-
# id int(11) NOT NULL auto_increment,
-
# account_id int(11) default NULL,
-
# name varchar default NULL,
-
# PRIMARY KEY (id)
-
# )
-
#
-
# CREATE TABLE accounts (
-
# id int(11) NOT NULL auto_increment,
-
# name varchar default NULL,
-
# PRIMARY KEY (id)
-
# )
-
#
-
# == Unsaved objects and associations
-
#
-
# You can manipulate objects and associations before they are saved to the database, but
-
# there is some special behavior you should be aware of, mostly involving the saving of
-
# associated objects.
-
#
-
# You can set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
-
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
-
# to +true+ will _always_ save the members, whereas setting it to +false+ will
-
# _never_ save the members. More details about :autosave option is available at
-
# autosave_association.rb .
-
#
-
# === One-to-one associations
-
#
-
# * Assigning an object to a +has_one+ association automatically saves that object and
-
# the object being replaced (if there is one), in order to update their foreign
-
# keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
-
# * If either of these saves fail (due to one of the objects being invalid), an
-
# <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
-
# cancelled.
-
# * If you wish to assign an object to a +has_one+ association without saving it,
-
# use the <tt>build_association</tt> method (documented below). The object being
-
# replaced will still be saved to update its foreign key.
-
# * Assigning an object to a +belongs_to+ association does not save the object, since
-
# the foreign key field belongs on the parent. It does not save the parent either.
-
#
-
# === Collections
-
#
-
# * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically
-
# saves that object, except if the parent object (the owner of the collection) is not yet
-
# stored in the database.
-
# * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
-
# fails, then <tt>push</tt> returns +false+.
-
# * If saving fails while replacing the collection (via <tt>association=</tt>), an
-
# <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
-
# cancelled.
-
# * You can add an object to a collection without automatically saving it by using the
-
# <tt>collection.build</tt> method (documented below).
-
# * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
-
# saved when the parent is saved.
-
#
-
# == Customizing the query
-
#
-
# Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
-
# to customize them. For example, to add a condition:
-
#
-
# class Blog < ActiveRecord::Base
-
# has_many :published_posts, -> { where published: true }, class_name: 'Post'
-
# end
-
#
-
# Inside the <tt>-> { ... }</tt> block you can use all of the usual <tt>Relation</tt> methods.
-
#
-
# === Accessing the owner object
-
#
-
# Sometimes it is useful to have access to the owner object when building the query. The owner
-
# is passed as a parameter to the block. For example, the following association would find all
-
# events that occur on the user's birthday:
-
#
-
# class User < ActiveRecord::Base
-
# has_many :birthday_events, ->(user) { where starts_on: user.birthday }, class_name: 'Event'
-
# end
-
#
-
# == Association callbacks
-
#
-
# Similar to the normal callbacks that hook into the life cycle of an Active Record object,
-
# you can also define callbacks that get triggered when you add an object to or remove an
-
# object from an association collection.
-
#
-
# class Project
-
# has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
-
#
-
# def evaluate_velocity(developer)
-
# ...
-
# end
-
# end
-
#
-
# It's possible to stack callbacks by passing them as an array. Example:
-
#
-
# class Project
-
# has_and_belongs_to_many :developers,
-
# :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
-
# end
-
#
-
# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
-
#
-
# Should any of the +before_add+ callbacks throw an exception, the object does not get
-
# added to the collection. Same with the +before_remove+ callbacks; if an exception is
-
# thrown the object doesn't get removed.
-
#
-
# == Association extensions
-
#
-
# The proxy objects that control the access to associations can be extended through anonymous
-
# modules. This is especially beneficial for adding new finders, creators, and other
-
# factory-type methods that are only used as part of this association.
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :people do
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by_first_name_and_last_name(first_name, last_name)
-
# end
-
# end
-
# end
-
#
-
# person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
-
# person.first_name # => "David"
-
# person.last_name # => "Heinemeier Hansson"
-
#
-
# If you need to share the same extensions between many associations, you can use a named
-
# extension module.
-
#
-
# module FindOrCreateByNameExtension
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by_first_name_and_last_name(first_name, last_name)
-
# end
-
# end
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :people, -> { extending FindOrCreateByNameExtension }
-
# end
-
#
-
# class Company < ActiveRecord::Base
-
# has_many :people, -> { extending FindOrCreateByNameExtension }
-
# end
-
#
-
# Some extensions can only be made to work with knowledge of the association's internals.
-
# Extensions can access relevant state using the following methods (where +items+ is the
-
# name of the association):
-
#
-
# * <tt>record.association(:items).owner</tt> - Returns the object the association is part of.
-
# * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association.
-
# * <tt>record.association(:items).target</tt> - Returns the associated object for +belongs_to+ and +has_one+, or
-
# the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
-
#
-
# However, inside the actual extension code, you will not have access to the <tt>record</tt> as
-
# above. In this case, you can access <tt>proxy_association</tt>. For example,
-
# <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
-
# the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
-
# association extensions.
-
#
-
# == Association Join Models
-
#
-
# Has Many associations can be configured with the <tt>:through</tt> option to use an
-
# explicit join model to retrieve the data. This operates similarly to a
-
# +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
-
# callbacks, and extra attributes on the join model. Consider the following schema:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :authorships
-
# has_many :books, :through => :authorships
-
# end
-
#
-
# class Authorship < ActiveRecord::Base
-
# belongs_to :author
-
# belongs_to :book
-
# end
-
#
-
# @author = Author.first
-
# @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
-
# @author.books # selects all books by using the Authorship join model
-
#
-
# You can also go through a +has_many+ association on the join model:
-
#
-
# class Firm < ActiveRecord::Base
-
# has_many :clients
-
# has_many :invoices, :through => :clients
-
# end
-
#
-
# class Client < ActiveRecord::Base
-
# belongs_to :firm
-
# has_many :invoices
-
# end
-
#
-
# class Invoice < ActiveRecord::Base
-
# belongs_to :client
-
# end
-
#
-
# @firm = Firm.first
-
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
-
# @firm.invoices # selects all invoices by going through the Client join model
-
#
-
# Similarly you can go through a +has_one+ association on the join model:
-
#
-
# class Group < ActiveRecord::Base
-
# has_many :users
-
# has_many :avatars, :through => :users
-
# end
-
#
-
# class User < ActiveRecord::Base
-
# belongs_to :group
-
# has_one :avatar
-
# end
-
#
-
# class Avatar < ActiveRecord::Base
-
# belongs_to :user
-
# end
-
#
-
# @group = Group.first
-
# @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
-
# @group.avatars # selects all avatars by going through the User join model.
-
#
-
# An important caveat with going through +has_one+ or +has_many+ associations on the
-
# join model is that these associations are *read-only*. For example, the following
-
# would not work following the previous example:
-
#
-
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
-
# @group.avatars.delete(@group.avatars.last) # so would this
-
#
-
# If you are using a +belongs_to+ on the join model, it is a good idea to set the
-
# <tt>:inverse_of</tt> option on the +belongs_to+, which will mean that the following example
-
# works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association):
-
#
-
# @post = Post.first
-
# @tag = @post.tags.build :name => "ruby"
-
# @tag.save
-
#
-
# The last line ought to save the through record (a <tt>Taggable</tt>). This will only work if the
-
# <tt>:inverse_of</tt> is set:
-
#
-
# class Taggable < ActiveRecord::Base
-
# belongs_to :post
-
# belongs_to :tag, :inverse_of => :taggings
-
# end
-
#
-
# == Nested Associations
-
#
-
# You can actually specify *any* association with the <tt>:through</tt> option, including an
-
# association which has a <tt>:through</tt> option itself. For example:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# has_many :comments, :through => :posts
-
# has_many :commenters, :through => :comments
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# end
-
#
-
# class Comment < ActiveRecord::Base
-
# belongs_to :commenter
-
# end
-
#
-
# @author = Author.first
-
# @author.commenters # => People who commented on posts written by the author
-
#
-
# An equivalent way of setting up this association this would be:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# has_many :commenters, :through => :posts
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# has_many :commenters, :through => :comments
-
# end
-
#
-
# class Comment < ActiveRecord::Base
-
# belongs_to :commenter
-
# end
-
#
-
# When using nested association, you will not be able to modify the association because there
-
# is not enough information to know what modification to make. For example, if you tried to
-
# add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
-
# intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
-
#
-
# == Polymorphic Associations
-
#
-
# Polymorphic associations on models are not restricted on what types of models they
-
# can be associated with. Rather, they specify an interface that a +has_many+ association
-
# must adhere to.
-
#
-
# class Asset < ActiveRecord::Base
-
# belongs_to :attachable, :polymorphic => true
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
-
# end
-
#
-
# @asset.attachable = @post
-
#
-
# This works by using a type column in addition to a foreign key to specify the associated
-
# record. In the Asset example, you'd need an +attachable_id+ integer column and an
-
# +attachable_type+ string column.
-
#
-
# Using polymorphic associations in combination with single table inheritance (STI) is
-
# a little tricky. In order for the associations to work as expected, ensure that you
-
# store the base model for the STI models in the type column of the polymorphic
-
# association. To continue with the asset example above, suppose there are guest posts
-
# and member posts that use the posts table for STI. In this case, there must be a +type+
-
# column in the posts table.
-
#
-
# class Asset < ActiveRecord::Base
-
# belongs_to :attachable, :polymorphic => true
-
#
-
# def attachable_type=(sType)
-
# super(sType.to_s.classify.constantize.base_class.to_s)
-
# end
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# # because we store "Post" in attachable_type now :dependent => :destroy will work
-
# has_many :assets, :as => :attachable, :dependent => :destroy
-
# end
-
#
-
# class GuestPost < Post
-
# end
-
#
-
# class MemberPost < Post
-
# end
-
#
-
# == Caching
-
#
-
# All of the methods are built on a simple caching principle that will keep the result
-
# of the last query around unless specifically instructed not to. The cache is even
-
# shared across methods to make it even cheaper to use the macro-added methods without
-
# worrying too much about performance at the first go.
-
#
-
# project.milestones # fetches milestones from the database
-
# project.milestones.size # uses the milestone cache
-
# project.milestones.empty? # uses the milestone cache
-
# project.milestones(true).size # fetches milestones from the database
-
# project.milestones # uses the milestone cache
-
#
-
# == Eager loading of associations
-
#
-
# Eager loading is a way to find objects of a certain class and a number of named associations.
-
# This is one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100
-
# posts that each need to display their author triggers 101 database queries. Through the
-
# use of eager loading, the 101 queries can be reduced to 2.
-
#
-
# class Post < ActiveRecord::Base
-
# belongs_to :author
-
# has_many :comments
-
# end
-
#
-
# Consider the following loop using the class above:
-
#
-
# Post.all.each do |post|
-
# puts "Post: " + post.title
-
# puts "Written by: " + post.author.name
-
# puts "Last comment on: " + post.comments.first.created_on
-
# end
-
#
-
# To iterate over these one hundred posts, we'll generate 201 database queries. Let's
-
# first just optimize it for retrieving the author:
-
#
-
# Post.includes(:author).each do |post|
-
#
-
# This references the name of the +belongs_to+ association that also used the <tt>:author</tt>
-
# symbol. After loading the posts, find will collect the +author_id+ from each one and load
-
# all the referenced authors with one query. Doing so will cut down the number of queries
-
# from 201 to 102.
-
#
-
# We can improve upon the situation further by referencing both associations in the finder with:
-
#
-
# Post.includes(:author, :comments).each do |post|
-
#
-
# This will load all comments with a single query. This reduces the total number of queries
-
# to 3. More generally the number of queries will be 1 plus the number of associations
-
# named (except if some of the associations are polymorphic +belongs_to+ - see below).
-
#
-
# To include a deep hierarchy of associations, use a hash:
-
#
-
# Post.includes(:author, {:comments => {:author => :gravatar}}).each do |post|
-
#
-
# That'll grab not only all the comments but all their authors and gravatar pictures.
-
# You can mix and match symbols, arrays and hashes in any combination to describe the
-
# associations you want to load.
-
#
-
# All of this power shouldn't fool you into thinking that you can pull out huge amounts
-
# of data with no performance penalty just because you've reduced the number of queries.
-
# The database still needs to send all the data to Active Record and it still needs to
-
# be processed. So it's no catch-all for performance problems, but it's a great way to
-
# cut down on the number of queries in a situation as the one described above.
-
#
-
# Since only one table is loaded at a time, conditions or orders cannot reference tables
-
# other than the main one. If this is the case Active Record falls back to the previously
-
# used LEFT OUTER JOIN based strategy. For example
-
#
-
# Post.includes([:author, :comments]).where(['comments.approved = ?', true]).all
-
#
-
# This will result in a single SQL query with joins along the lines of:
-
# <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
-
# <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
-
# like this can have unintended consequences.
-
# In the above example posts with no approved comments are not returned at all, because
-
# the conditions apply to the SQL statement as a whole and not just to the association.
-
# You must disambiguate column references for this fallback to happen, for example
-
# <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
-
#
-
# If you do want eager load only some members of an association it is usually more natural
-
# to include an association which has conditions defined on it:
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :approved_comments, -> { where approved: true }, :class_name => 'Comment'
-
# end
-
#
-
# Post.includes(:approved_comments)
-
#
-
# This will load posts and eager load the +approved_comments+ association, which contains
-
# only those comments that have been approved.
-
#
-
# If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
-
# returning all the associated objects:
-
#
-
# class Picture < ActiveRecord::Base
-
# has_many :most_recent_comments, -> { order('id DESC').limit(10) }, :class_name => 'Comment'
-
# end
-
#
-
# Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
-
#
-
# Eager loading is supported with polymorphic associations.
-
#
-
# class Address < ActiveRecord::Base
-
# belongs_to :addressable, :polymorphic => true
-
# end
-
#
-
# A call that tries to eager load the addressable model
-
#
-
# Address.includes(:addressable)
-
#
-
# This will execute one query to load the addresses and load the addressables with one
-
# query per addressable type.
-
# For example if all the addressables are either of class Person or Company then a total
-
# of 3 queries will be executed. The list of addressable types to load is determined on
-
# the back of the addresses loaded. This is not supported if Active Record has to fallback
-
# to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
-
# The reason is that the parent model's type is a column value so its corresponding table
-
# name cannot be put in the +FROM+/+JOIN+ clauses of that query.
-
#
-
# == Table Aliasing
-
#
-
# Active Record uses table aliasing in the case that a table is referenced multiple times
-
# in a join. If a table is referenced only once, the standard table name is used. The
-
# second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
-
# Indexes are appended for any more successive uses of the table name.
-
#
-
# Post.joins(:comments)
-
# # => SELECT ... FROM posts INNER JOIN comments ON ...
-
# Post.joins(:special_comments) # STI
-
# # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
-
# Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
-
# # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
-
#
-
# Acts as tree example:
-
#
-
# TreeMixin.joins(:children)
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# TreeMixin.joins(:children => :parent)
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# INNER JOIN parents_mixins ...
-
# TreeMixin.joins(:children => {:parent => :children})
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# INNER JOIN parents_mixins ...
-
# INNER JOIN mixins childrens_mixins_2
-
#
-
# Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
-
#
-
# Post.joins(:categories)
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# Post.joins(:categories => :posts)
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
-
# Post.joins(:categories => {:posts => :categories})
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
-
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
-
#
-
# If you wish to specify your own custom joins using <tt>joins</tt> method, those table
-
# names will take precedence over the eager associations:
-
#
-
# Post.joins(:comments).joins("inner join comments ...")
-
# # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
-
# Post.joins(:comments, :special_comments).joins("inner join comments ...")
-
# # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
-
# INNER JOIN comments special_comments_posts ...
-
# INNER JOIN comments ...
-
#
-
# Table aliases are automatically truncated according to the maximum length of table identifiers
-
# according to the specific database.
-
#
-
# == Modules
-
#
-
# By default, associations will look for objects within the current module scope. Consider:
-
#
-
# module MyApplication
-
# module Business
-
# class Firm < ActiveRecord::Base
-
# has_many :clients
-
# end
-
#
-
# class Client < ActiveRecord::Base; end
-
# end
-
# end
-
#
-
# When <tt>Firm#clients</tt> is called, it will in turn call
-
# <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
-
# If you want to associate with a class in another module scope, this can be done by
-
# specifying the complete class name.
-
#
-
# module MyApplication
-
# module Business
-
# class Firm < ActiveRecord::Base; end
-
# end
-
#
-
# module Billing
-
# class Account < ActiveRecord::Base
-
# belongs_to :firm, :class_name => "MyApplication::Business::Firm"
-
# end
-
# end
-
# end
-
#
-
# == Bi-directional associations
-
#
-
# When you specify an association there is usually an association on the associated model
-
# that specifies the same relationship in reverse. For example, with the following models:
-
#
-
# class Dungeon < ActiveRecord::Base
-
# has_many :traps
-
# has_one :evil_wizard
-
# end
-
#
-
# class Trap < ActiveRecord::Base
-
# belongs_to :dungeon
-
# end
-
#
-
# class EvilWizard < ActiveRecord::Base
-
# belongs_to :dungeon
-
# end
-
#
-
# The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
-
# the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
-
# is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
-
# Active Record doesn't know anything about these inverse relationships and so no object
-
# loading optimization is possible. For example:
-
#
-
# d = Dungeon.first
-
# t = d.traps.first
-
# d.level == t.dungeon.level # => true
-
# d.level = 10
-
# d.level == t.dungeon.level # => false
-
#
-
# The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
-
# the same object data from the database, but are actually different in-memory copies
-
# of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
-
# Active Record about inverse relationships and it will optimise object loading. For
-
# example, if we changed our model definitions to:
-
#
-
# class Dungeon < ActiveRecord::Base
-
# has_many :traps, :inverse_of => :dungeon
-
# has_one :evil_wizard, :inverse_of => :dungeon
-
# end
-
#
-
# class Trap < ActiveRecord::Base
-
# belongs_to :dungeon, :inverse_of => :traps
-
# end
-
#
-
# class EvilWizard < ActiveRecord::Base
-
# belongs_to :dungeon, :inverse_of => :evil_wizard
-
# end
-
#
-
# Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same
-
# in-memory instance and our final <tt>d.level == t.dungeon.level</tt> will return +true+.
-
#
-
# There are limitations to <tt>:inverse_of</tt> support:
-
#
-
# * does not work with <tt>:through</tt> associations.
-
# * does not work with <tt>:polymorphic</tt> associations.
-
# * for +belongs_to+ associations +has_many+ inverse associations are ignored.
-
#
-
# == Deleting from associations
-
#
-
# === Dependent associations
-
#
-
# +has_many+, +has_one+ and +belongs_to+ associations support the <tt>:dependent</tt> option.
-
# This allows you to specify that associated records should be deleted when the owner is
-
# deleted.
-
#
-
# For example:
-
#
-
# class Author
-
# has_many :posts, :dependent => :destroy
-
# end
-
# Author.find(1).destroy # => Will destroy all of the author's posts, too
-
#
-
# The <tt>:dependent</tt> option can have different values which specify how the deletion
-
# is done. For more information, see the documentation for this option on the different
-
# specific association types. When no option is given, the behaviour is to do nothing
-
# with the associated records when destroying a record.
-
#
-
# Note that <tt>:dependent</tt> is implemented using Rails' callback
-
# system, which works by processing callbacks in order. Therefore, other
-
# callbacks declared either before or after the <tt>:dependent</tt> option
-
# can affect what it does.
-
#
-
# === Delete or destroy?
-
#
-
# +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>,
-
# <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>.
-
#
-
# For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they
-
# cause the records in the join table to be removed.
-
#
-
# For +has_many+, <tt>destroy</tt> will always call the <tt>destroy</tt> method of the
-
# record(s) being removed so that callbacks are run. However <tt>delete</tt> will either
-
# do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
-
# if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
-
# The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for
-
# +has_many+ <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
-
# the join records, without running their callbacks).
-
#
-
# There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that
-
# it returns the association rather than the records which have been deleted.
-
#
-
# === What gets deleted?
-
#
-
# There is a potential pitfall here: +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>
-
# associations have records in join tables, as well as the associated records. So when we
-
# call one of these deletion methods, what exactly should be deleted?
-
#
-
# The answer is that it is assumed that deletion on an association is about removing the
-
# <i>link</i> between the owner and the associated object(s), rather than necessarily the
-
# associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
-
# <tt>:through</tt>, the join records will be deleted, but the associated records won't.
-
#
-
# This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by_name('food'))</tt>
-
# you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
-
# to be removed from the database.
-
#
-
# However, there are examples where this strategy doesn't make sense. For example, suppose
-
# a person has many projects, and each project has many tasks. If we deleted one of a person's
-
# tasks, we would probably not want the project to be deleted. In this scenario, the delete method
-
# won't actually work: it can only be used if the association on the join model is a
-
# +belongs_to+. In other situations you are expected to perform operations directly on
-
# either the associated records or the <tt>:through</tt> association.
-
#
-
# With a regular +has_many+ there is no distinction between the "associated records"
-
# and the "link", so there is only one choice for what gets deleted.
-
#
-
# With +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>, if you want to delete the
-
# associated records themselves, you can always do something along the lines of
-
# <tt>person.tasks.each(&:destroy)</tt>.
-
#
-
# == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
-
#
-
# If you attempt to assign an object to an association that doesn't match the inferred
-
# or specified <tt>:class_name</tt>, you'll get an <tt>ActiveRecord::AssociationTypeMismatch</tt>.
-
#
-
# == Options
-
#
-
# All of the association macros can be specialized through options. This makes cases
-
# more complex than the simple and guessable ones possible.
-
1
module ClassMethods
-
# Specifies a one-to-many association. The following methods for retrieval and query of
-
# collections of associated objects will be added:
-
#
-
# [collection(force_reload = false)]
-
# Returns an array of all the associated objects.
-
# An empty array is returned if none are found.
-
# [collection<<(object, ...)]
-
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
-
# Note that this operation instantly fires update sql without waiting for the save or update call on the
-
# parent object.
-
# [collection.delete(object, ...)]
-
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
-
# Objects will be in addition destroyed if they're associated with <tt>:dependent => :destroy</tt>,
-
# and deleted if they're associated with <tt>:dependent => :delete_all</tt>.
-
#
-
# If the <tt>:through</tt> option is used, then the join records are deleted (rather than
-
# nullified) by default, but you can specify <tt>:dependent => :destroy</tt> or
-
# <tt>:dependent => :nullify</tt> to override this.
-
# [collection.destroy(object, ...)]
-
# Removes one or more objects from the collection by running <tt>destroy</tt> on
-
# each record, regardless of any dependent option, ensuring callbacks are run.
-
#
-
# If the <tt>:through</tt> option is used, then the join records are destroyed
-
# instead, not the objects themselves.
-
# [collection=objects]
-
# Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
-
# option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
-
# direct.
-
# [collection_singular_ids]
-
# Returns an array of the associated objects' ids
-
# [collection_singular_ids=ids]
-
# Replace the collection with the objects identified by the primary keys in +ids+. This
-
# method loads the models and calls <tt>collection=</tt>. See above.
-
# [collection.clear]
-
# Removes every object from the collection. This destroys the associated objects if they
-
# are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the
-
# database if <tt>:dependent => :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
-
# If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
-
# Join models are directly deleted.
-
# [collection.empty?]
-
# Returns +true+ if there are no associated objects.
-
# [collection.size]
-
# Returns the number of associated objects.
-
# [collection.find(...)]
-
# Finds an associated object according to the same rules as ActiveRecord::Base.find.
-
# [collection.exists?(...)]
-
# Checks whether an associated object with the given conditions exists.
-
# Uses the same rules as ActiveRecord::Base.exists?.
-
# [collection.build(attributes = {}, ...)]
-
# Returns one or more new objects of the collection type that have been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but have not yet
-
# been saved.
-
# [collection.create(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that has already
-
# been saved (if it passed the validation). *Note*: This only works if the base model
-
# already exists in the DB, not if it is a new (unsaved) record!
-
#
-
# (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
-
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
-
#
-
# === Example
-
#
-
# Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
-
# * <tt>Firm#clients</tt> (similar to <tt>Clients.all :conditions => ["firm_id = ?", id]</tt>)
-
# * <tt>Firm#clients<<</tt>
-
# * <tt>Firm#clients.delete</tt>
-
# * <tt>Firm#clients.destroy</tt>
-
# * <tt>Firm#clients=</tt>
-
# * <tt>Firm#client_ids</tt>
-
# * <tt>Firm#client_ids=</tt>
-
# * <tt>Firm#clients.clear</tt>
-
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
-
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
-
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
-
# * <tt>Firm#clients.exists?(:name => 'ACME')</tt> (similar to <tt>Client.exists?(:name => 'ACME', :firm_id => firm.id)</tt>)
-
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
-
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
-
# The declaration can also include an options hash to specialize the behavior of the association.
-
#
-
# === Options
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_many :products</tt> will by default be linked
-
# to the Product class, but if the real class name is SpecialProduct, you'll have to
-
# specify it with this option.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
-
# association will use "person_id" as the default <tt>:foreign_key</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key used for the association. By default this is +id+.
-
# [:dependent]
-
# Controls what happens to the associated objects when
-
# their owner is destroyed. Note that these are implemented as
-
# callbacks, and Rails executes callbacks in order. Therefore, other
-
# similar callbacks may affect the :dependent behavior, and the
-
# :dependent behavior may affect other callbacks.
-
#
-
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed
-
# * <tt>:delete_all</tt> causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
-
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
-
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
-
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
-
#
-
# If using with the <tt>:through</tt> option, the association on the join model must be
-
# a +belongs_to+, and the records which get deleted are the join records, rather than
-
# the associated records.
-
# [:counter_cache]
-
# This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
-
# when you customized the name of your <tt>:counter_cache</tt> on the <tt>belongs_to</tt> association.
-
# [:as]
-
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
-
# [:through]
-
# Specifies an association through which to perform the query. This can be any other type
-
# of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>,
-
# <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the
-
# source reflection.
-
#
-
# If the association on the join model is a +belongs_to+, the collection can be modified
-
# and the records on the <tt>:through</tt> model will be automatically created and removed
-
# as appropriate. Otherwise, the collection is read-only, so you should manipulate the
-
# <tt>:through</tt> association directly.
-
#
-
# If you are going to modify the association (rather than just read from it), then it is
-
# a good idea to set the <tt>:inverse_of</tt> option on the source association on the
-
# join model. This allows associated records to be built which will automatically create
-
# the appropriate join model records when they are saved. (See the 'Association Join Models'
-
# section above.)
-
# [:source]
-
# Specifies the source association name used by <tt>has_many :through</tt> queries.
-
# Only use it if the name cannot be inferred from the association.
-
# <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
-
# <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
-
# [:source_type]
-
# Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
-
# association is a polymorphic +belongs_to+.
-
# [:validate]
-
# If +false+, don't validate the associated objects when saving the parent object. true by default.
-
# [:autosave]
-
# If true, always save the associated objects or destroy them if marked for destruction,
-
# when saving the parent object. If false, never save or destroy the associated objects.
-
# By default, only save associated objects that are new records. This option is implemented as a
-
# before_save callback. Because callbacks are run in the order they are defined, associated objects
-
# may need to be explicitly saved in any user-defined before_save callbacks.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:inverse_of]
-
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
-
# that is the inverse of this <tt>has_many</tt> association. Does not work in combination
-
# with <tt>:through</tt> or <tt>:as</tt> options.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
#
-
# Option examples:
-
# has_many :comments, -> { order "posted_on" }
-
# has_many :comments, -> { includes :author }
-
# has_many :people, -> { where("deleted = 0").order("name") }, class_name: "Person"
-
# has_many :tracks, -> { order "position" }, dependent: :destroy
-
# has_many :comments, dependent: :nullify
-
# has_many :tags, as: :taggable
-
# has_many :reports, -> { readonly }
-
# has_many :subscribers, through: :subscriptions, source: :user
-
1
def has_many(name, scope = nil, options = {}, &extension)
-
3
Builder::HasMany.build(self, name, scope, options, &extension)
-
end
-
-
# Specifies a one-to-one association with another class. This method should only be used
-
# if the other class contains the foreign key. If the current class contains the foreign key,
-
# then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview
-
# on when to use has_one and when to use belongs_to.
-
#
-
# The following methods for retrieval and query of a single associated object will be added:
-
#
-
# [association(force_reload = false)]
-
# Returns the associated object. +nil+ is returned if none is found.
-
# [association=(associate)]
-
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
-
# and saves the associate object.
-
# [build_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but has not
-
# yet been saved.
-
# [create_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that
-
# has already been saved (if it passed the validation).
-
# [create_association!(attributes = {})]
-
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
-
# if the record is invalid.
-
#
-
# (+association+ is replaced with the symbol passed as the first argument, so
-
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
-
#
-
# === Example
-
#
-
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
-
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(:conditions => "account_id = #{id}")</tt>)
-
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
-
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
-
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
-
# * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
-
#
-
# === Options
-
#
-
# The declaration can also include an options hash to specialize the behavior of the association.
-
#
-
# Options are:
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
-
# if the real class name is Person, you'll have to specify it with this option.
-
# [:dependent]
-
# Controls what happens to the associated object when
-
# its owner is destroyed:
-
#
-
# * <tt>:destroy</tt> causes the associated object to also be destroyed
-
# * <tt>:delete</tt> causes the asssociated object to be deleted directly from the database (so callbacks will not execute)
-
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
-
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
-
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
-
# will use "person_id" as the default <tt>:foreign_key</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key used for the association. By default this is +id+.
-
# [:as]
-
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
-
# [:through]
-
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
-
# <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
-
# source reflection. You can only use a <tt>:through</tt> query through a <tt>has_one</tt>
-
# or <tt>belongs_to</tt> association on the join model.
-
# [:source]
-
# Specifies the source association name used by <tt>has_one :through</tt> queries.
-
# Only use it if the name cannot be inferred from the association.
-
# <tt>has_one :favorite, :through => :favorites</tt> will look for a
-
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
-
# [:source_type]
-
# Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
-
# association is a polymorphic +belongs_to+.
-
# [:validate]
-
# If +false+, don't validate the associated object when saving the parent object. +false+ by default.
-
# [:autosave]
-
# If true, always save the associated object or destroy it if marked for destruction,
-
# when saving the parent object. If false, never save or destroy the associated object.
-
# By default, only save the associated object if it's a new record.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:inverse_of]
-
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
-
# that is the inverse of this <tt>has_one</tt> association. Does not work in combination
-
# with <tt>:through</tt> or <tt>:as</tt> options.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
#
-
# Option examples:
-
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
-
# has_one :credit_card, :dependent => :nullify # updates the associated records foreign
-
# # key value to NULL rather than destroying it
-
# has_one :last_comment, -> { order 'posted_on' }, :class_name => "Comment"
-
# has_one :project_manager, -> { where role: 'project_manager' }, :class_name => "Person"
-
# has_one :attachment, as: :attachable
-
# has_one :boss, readonly: :true
-
# has_one :club, through: :membership
-
# has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
-
1
def has_one(name, scope = nil, options = {})
-
1
Builder::HasOne.build(self, name, scope, options)
-
end
-
-
# Specifies a one-to-one association with another class. This method should only be used
-
# if this class contains the foreign key. If the other class contains the foreign key,
-
# then you should use +has_one+ instead. See also ActiveRecord::Associations::ClassMethods's overview
-
# on when to use +has_one+ and when to use +belongs_to+.
-
#
-
# Methods will be added for retrieval and query for a single associated object, for which
-
# this object holds an id:
-
#
-
# [association(force_reload = false)]
-
# Returns the associated object. +nil+ is returned if none is found.
-
# [association=(associate)]
-
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
-
# [build_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
-
# [create_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that
-
# has already been saved (if it passed the validation).
-
# [create_association!(attributes = {})]
-
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
-
# if the record is invalid.
-
#
-
# (+association+ is replaced with the symbol passed as the first argument, so
-
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
-
#
-
# === Example
-
#
-
# A Post class declares <tt>belongs_to :author</tt>, which will add:
-
# * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
-
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
-
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
-
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
-
# * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
-
# The declaration can also include an options hash to specialize the behavior of the association.
-
#
-
# === Options
-
#
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but
-
# if the real class name is Person, you'll have to specify it with this option.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
-
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
-
# <tt>belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key
-
# of "favorite_person_id".
-
# [:foreign_type]
-
# Specify the column used to store the associated object's type, if this is a polymorphic
-
# association. By default this is guessed to be the name of the association with a "_type"
-
# suffix. So a class that defines a <tt>belongs_to :taggable, :polymorphic => true</tt>
-
# association will use "taggable_type" as the default <tt>:foreign_type</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key of associated object used for the association.
-
# By default this is id.
-
# [:dependent]
-
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
-
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
-
# This option should not be specified when <tt>belongs_to</tt> is used in conjunction with
-
# a <tt>has_many</tt> relationship on another class because of the potential to leave
-
# orphaned records behind.
-
# [:counter_cache]
-
# Caches the number of belonging objects on the associate class through the use of +increment_counter+
-
# and +decrement_counter+. The counter cache is incremented when an object of this
-
# class is created and decremented when it's destroyed. This requires that a column
-
# named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
-
# is used on the associate class (such as a Post class) - that is the migration for
-
# <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
-
# return the count cached, see note below). You can also specify a custom counter
-
# cache column by providing a column name instead of a +true+/+false+ value to this
-
# option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
-
# Note: Specifying a counter cache will add it to that model's list of readonly attributes
-
# using +attr_readonly+.
-
# [:polymorphic]
-
# Specify this association is a polymorphic association by passing +true+.
-
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
-
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
-
# [:validate]
-
# If +false+, don't validate the associated objects when saving the parent object. +false+ by default.
-
# [:autosave]
-
# If true, always save the associated object or destroy it if marked for destruction, when
-
# saving the parent object.
-
# If false, never save or destroy the associated object.
-
# By default, only save the associated object if it's a new record.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:touch]
-
# If true, the associated object will be touched (the updated_at/on attributes set to now)
-
# when this record is either saved or destroyed. If you specify a symbol, that attribute
-
# will be updated with the current time in addition to the updated_at/on attribute.
-
# [:inverse_of]
-
# Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated
-
# object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
-
# combination with the <tt>:polymorphic</tt> options.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
#
-
# Option examples:
-
# belongs_to :firm, foreign_key: "client_of"
-
# belongs_to :person, primary_key: "name", foreign_key: "person_name"
-
# belongs_to :author, class_name: "Person", foreign_key: "author_id"
-
# belongs_to :valid_coupon, ->(o) { where "discounts > #{o.payments_count}" },
-
# class_name: "Coupon", foreign_key: "coupon_id"
-
# belongs_to :attachable, polymorphic: true
-
# belongs_to :project, readonly: true
-
# belongs_to :post, counter_cache: true
-
# belongs_to :company, touch: true
-
# belongs_to :company, touch: :employees_last_updated_at
-
1
def belongs_to(name, scope = nil, options = {})
-
3
Builder::BelongsTo.build(self, name, scope, options)
-
end
-
-
# Specifies a many-to-many relationship with another class. This associates two classes via an
-
# intermediate join table. Unless the join table is explicitly specified as an option, it is
-
# guessed using the lexical order of the class names. So a join between Developer and Project
-
# will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically.
-
# Note that this precedence is calculated using the <tt><</tt> operator for String. This
-
# means that if the strings are of different lengths, and the strings are equal when compared
-
# up to the shortest length, then the longer string is considered of higher
-
# lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
-
# to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
-
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
-
# custom <tt>:join_table</tt> option if you need to.
-
#
-
# The join table should not have a primary key or a model associated with it. You must manually generate the
-
# join table with a migration such as this:
-
#
-
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
-
# def change
-
# create_table :developers_projects, :id => false do |t|
-
# t.integer :developer_id
-
# t.integer :project_id
-
# end
-
# end
-
# end
-
#
-
# It's also a good idea to add indexes to each of those columns to speed up the joins process.
-
# However, in MySQL it is advised to add a compound index for both of the columns as MySQL only
-
# uses one index per table during the lookup.
-
#
-
# Adds the following methods for retrieval and query:
-
#
-
# [collection(force_reload = false)]
-
# Returns an array of all the associated objects.
-
# An empty array is returned if none are found.
-
# [collection<<(object, ...)]
-
# Adds one or more objects to the collection by creating associations in the join table
-
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
-
# Note that this operation instantly fires update sql without waiting for the save or update call on the
-
# parent object.
-
# [collection.delete(object, ...)]
-
# Removes one or more objects from the collection by removing their associations from the join table.
-
# This does not destroy the objects.
-
# [collection.destroy(object, ...)]
-
# Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option.
-
# This does not destroy the objects.
-
# [collection=objects]
-
# Replaces the collection's content by deleting and adding objects as appropriate.
-
# [collection_singular_ids]
-
# Returns an array of the associated objects' ids.
-
# [collection_singular_ids=ids]
-
# Replace the collection by the objects identified by the primary keys in +ids+.
-
# [collection.clear]
-
# Removes every object from the collection. This does not destroy the objects.
-
# [collection.empty?]
-
# Returns +true+ if there are no associated objects.
-
# [collection.size]
-
# Returns the number of associated objects.
-
# [collection.find(id)]
-
# Finds an associated object responding to the +id+ and that
-
# meets the condition that it has to be associated with this object.
-
# Uses the same rules as ActiveRecord::Base.find.
-
# [collection.exists?(...)]
-
# Checks whether an associated object with the given conditions exists.
-
# Uses the same rules as ActiveRecord::Base.exists?.
-
# [collection.build(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
-
# [collection.create(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+, linked to this object through the join table, and that has already been
-
# saved (if it passed the validation).
-
#
-
# (+collection+ is replaced with the symbol passed as the first argument, so
-
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.)
-
#
-
# === Example
-
#
-
# A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
-
# * <tt>Developer#projects</tt>
-
# * <tt>Developer#projects<<</tt>
-
# * <tt>Developer#projects.delete</tt>
-
# * <tt>Developer#projects.destroy</tt>
-
# * <tt>Developer#projects=</tt>
-
# * <tt>Developer#project_ids</tt>
-
# * <tt>Developer#project_ids=</tt>
-
# * <tt>Developer#projects.clear</tt>
-
# * <tt>Developer#projects.empty?</tt>
-
# * <tt>Developer#projects.size</tt>
-
# * <tt>Developer#projects.find(id)</tt>
-
# * <tt>Developer#projects.exists?(...)</tt>
-
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
-
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
-
# The declaration may include an options hash to specialize the behavior of the association.
-
#
-
# === Options
-
#
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
-
# Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
-
# [:join_table]
-
# Specify the name of the join table if the default based on lexical order isn't what you want.
-
# <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method
-
# MUST be declared underneath any +has_and_belongs_to_many+ declaration in order to work.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes
-
# a +has_and_belongs_to_many+ association to Project will use "person_id" as the
-
# default <tt>:foreign_key</tt>.
-
# [:association_foreign_key]
-
# Specify the foreign key used for the association on the receiving side of the association.
-
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
-
# So if a Person class makes a +has_and_belongs_to_many+ association to Project,
-
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
-
# [:readonly]
-
# If true, all the associated objects are readonly through the association.
-
# [:validate]
-
# If +false+, don't validate the associated objects when saving the parent object. +true+ by default.
-
# [:autosave]
-
# If true, always save the associated objects or destroy them if marked for destruction, when
-
# saving the parent object.
-
# If false, never save or destroy the associated objects.
-
# By default, only save associated objects that are new records.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
#
-
# Option examples:
-
# has_and_belongs_to_many :projects
-
# has_and_belongs_to_many :projects, -> { includes :milestones, :manager }
-
# has_and_belongs_to_many :nations, class_name: "Country"
-
# has_and_belongs_to_many :categories, join_table: "prods_cats"
-
# has_and_belongs_to_many :categories, -> { readonly }
-
1
def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
-
2
Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class Association #:nodoc:
-
1
class << self
-
1
attr_accessor :valid_options
-
end
-
-
1
self.valid_options = [:class_name, :foreign_key, :validate]
-
-
1
attr_reader :model, :name, :scope, :options, :reflection
-
-
1
def self.build(*args, &block)
-
9
new(*args, &block).build
-
end
-
-
1
def initialize(model, name, scope, options)
-
9
@model = model
-
9
@name = name
-
-
9
if scope.is_a?(Hash)
-
@scope = nil
-
@options = scope
-
else
-
9
@scope = scope
-
9
@options = options
-
end
-
-
9
if @scope && @scope.arity == 0
-
2
prev_scope = @scope
-
2
@scope = proc { instance_exec(&prev_scope) }
-
end
-
end
-
-
1
def mixin
-
33
@model.generated_feature_methods
-
end
-
-
2
include Module.new { def build; end }
-
-
1
def build
-
9
validate_options
-
9
define_accessors
-
9
configure_dependency if options[:dependent]
-
9
@reflection = model.create_reflection(macro, name, scope, options, model)
-
9
super # provides an extension point
-
9
@reflection
-
end
-
-
1
def macro
-
raise NotImplementedError
-
end
-
-
1
def valid_options
-
9
Association.valid_options
-
end
-
-
1
def validate_options
-
9
options.assert_valid_keys(valid_options)
-
end
-
-
1
def define_accessors
-
9
define_readers
-
9
define_writers
-
end
-
-
1
def define_readers
-
9
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}(*args)
-
association(:#{name}).reader(*args)
-
end
-
CODE
-
end
-
-
1
def define_writers
-
9
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}=(value)
-
association(:#{name}).writer(value)
-
end
-
CODE
-
end
-
-
1
def configure_dependency
-
1
unless valid_dependent_options.include? options[:dependent]
-
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
-
end
-
-
1
if options[:dependent] == :restrict
-
ActiveSupport::Deprecation.warn(
-
"The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \
-
"provides the same functionality."
-
)
-
end
-
-
1
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{macro}_dependent_for_#{name}
-
association(:#{name}).handle_dependency
-
end
-
CODE
-
-
1
model.before_destroy "#{macro}_dependent_for_#{name}"
-
end
-
-
1
def valid_dependent_options
-
raise NotImplementedError
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class BelongsTo < SingularAssociation #:nodoc:
-
1
def macro
-
3
:belongs_to
-
end
-
-
1
def valid_options
-
3
super + [:foreign_type, :polymorphic, :touch]
-
end
-
-
1
def constructable?
-
3
!options[:polymorphic]
-
end
-
-
1
def build
-
3
reflection = super
-
3
add_counter_cache_callbacks(reflection) if options[:counter_cache]
-
3
add_touch_callbacks(reflection) if options[:touch]
-
3
reflection
-
end
-
-
1
def add_counter_cache_callbacks(reflection)
-
cache_column = reflection.counter_cache_column
-
-
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def belongs_to_counter_cache_after_create_for_#{name}
-
record = #{name}
-
record.class.increment_counter(:#{cache_column}, record.id) unless record.nil?
-
end
-
-
def belongs_to_counter_cache_before_destroy_for_#{name}
-
unless marked_for_destruction?
-
record = #{name}
-
record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
-
end
-
end
-
CODE
-
-
model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
-
model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
-
-
klass = reflection.class_name.safe_constantize
-
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
-
end
-
-
1
def add_touch_callbacks(reflection)
-
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def belongs_to_touch_after_save_or_destroy_for_#{name}
-
record = #{name}
-
-
unless record.nil?
-
record.touch #{options[:touch].inspect if options[:touch] != true}
-
end
-
end
-
CODE
-
-
model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
-
model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
-
model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
-
end
-
-
1
def valid_dependent_options
-
[:destroy, :delete]
-
end
-
end
-
end
-
1
require 'active_record/associations'
-
-
1
module ActiveRecord::Associations::Builder
-
1
class CollectionAssociation < Association #:nodoc:
-
-
1
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
-
-
1
def valid_options
-
5
super + [:table_name, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove]
-
end
-
-
1
attr_reader :block_extension, :extension_module
-
-
1
def initialize(*args, &extension)
-
5
super(*args)
-
5
@block_extension = extension
-
end
-
-
1
def build
-
5
show_deprecation_warnings
-
5
wrap_block_extension
-
5
reflection = super
-
25
CALLBACKS.each { |callback_name| define_callback(callback_name) }
-
5
reflection
-
end
-
-
1
def writable?
-
true
-
end
-
-
1
def show_deprecation_warnings
-
5
[:finder_sql, :counter_sql].each do |name|
-
10
if options.include? name
-
ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using scopes).")
-
end
-
end
-
end
-
-
1
def wrap_block_extension
-
5
if block_extension
-
@extension_module = mod = Module.new(&block_extension)
-
silence_warnings do
-
model.parent.const_set(extension_module_name, mod)
-
end
-
-
prev_scope = @scope
-
-
if prev_scope
-
@scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
-
else
-
@scope = proc { extending(mod) }
-
end
-
end
-
end
-
-
1
def extension_module_name
-
@extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
-
end
-
-
1
def define_callback(callback_name)
-
20
full_callback_name = "#{callback_name}_for_#{name}"
-
-
# TODO : why do i need method_defined? I think its because of the inheritance chain
-
20
model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
-
20
model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
-
end
-
-
1
def define_readers
-
5
super
-
-
5
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name.to_s.singularize}_ids
-
association(:#{name}).ids_reader
-
end
-
CODE
-
end
-
-
1
def define_writers
-
5
super
-
-
5
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name.to_s.singularize}_ids=(ids)
-
association(:#{name}).ids_writer(ids)
-
end
-
CODE
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class HasAndBelongsToMany < CollectionAssociation #:nodoc:
-
1
def macro
-
2
:has_and_belongs_to_many
-
end
-
-
1
def valid_options
-
2
super + [:join_table, :association_foreign_key, :delete_sql, :insert_sql]
-
end
-
-
1
def build
-
2
reflection = super
-
2
define_destroy_hook
-
2
reflection
-
end
-
-
1
def show_deprecation_warnings
-
2
super
-
-
2
[:delete_sql, :insert_sql].each do |name|
-
4
if options.include? name
-
ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using has_many :through).")
-
end
-
end
-
end
-
-
1
def define_destroy_hook
-
2
name = self.name
-
2
model.send(:include, Module.new {
-
2
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def destroy_associations
-
association(:#{name}).delete_all
-
super
-
end
-
RUBY
-
})
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class HasMany < CollectionAssociation #:nodoc:
-
1
def macro
-
5
:has_many
-
end
-
-
1
def valid_options
-
3
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
-
end
-
-
1
def valid_dependent_options
-
1
[:destroy, :delete_all, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class HasOne < SingularAssociation #:nodoc:
-
1
def macro
-
1
:has_one
-
end
-
-
1
def valid_options
-
1
valid = super + [:order, :as]
-
1
valid += [:through, :source, :source_type] if options[:through]
-
1
valid
-
end
-
-
1
def constructable?
-
1
!options[:through]
-
end
-
-
1
def configure_dependency
-
super unless options[:through]
-
end
-
-
1
def valid_dependent_options
-
[:destroy, :delete, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class SingularAssociation < Association #:nodoc:
-
1
def valid_options
-
4
super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of]
-
end
-
-
1
def constructable?
-
true
-
end
-
-
1
def define_accessors
-
4
super
-
4
define_constructors if constructable?
-
end
-
-
1
def define_constructors
-
4
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def build_#{name}(*args, &block)
-
association(:#{name}).build(*args, &block)
-
end
-
-
def create_#{name}(*args, &block)
-
association(:#{name}).create(*args, &block)
-
end
-
-
def create_#{name}!(*args, &block)
-
association(:#{name}).create!(*args, &block)
-
end
-
CODE
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
# Association proxies in Active Record are middlemen between the object that
-
# holds the association, known as the <tt>@owner</tt>, and the actual associated
-
# object, known as the <tt>@target</tt>. The kind of association any proxy is
-
# about is available in <tt>@reflection</tt>. That's an instance of the class
-
# ActiveRecord::Reflection::AssociationReflection.
-
#
-
# For example, given
-
#
-
# class Blog < ActiveRecord::Base
-
# has_many :posts
-
# end
-
#
-
# blog = Blog.first
-
#
-
# the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
-
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
-
# the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
-
#
-
# This class delegates unknown methods to <tt>@target</tt> via
-
# <tt>method_missing</tt>.
-
#
-
# The <tt>@target</tt> object is not \loaded until needed. For example,
-
#
-
# blog.posts.count
-
#
-
# is computed directly through SQL and does not trigger by itself the
-
# instantiation of the actual post records.
-
1
class CollectionProxy < Relation
-
1
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
-
-
1
def initialize(association) #:nodoc:
-
@association = association
-
super association.klass, association.klass.arel_table
-
merge! association.scope(nullify: false)
-
end
-
-
1
def target
-
@association.target
-
end
-
-
1
def load_target
-
@association.load_target
-
end
-
-
# Returns +true+ if the association has been loaded, otherwise +false+.
-
#
-
# person.pets.loaded? # => false
-
# person.pets
-
# person.pets.loaded? # => true
-
1
def loaded?
-
@association.loaded?
-
end
-
-
# Works in two ways.
-
#
-
# *First:* Specify a subset of fields to be selected from the result set.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.select(:name)
-
# # => [
-
# # #<Pet id: nil, name: "Fancy-Fancy">,
-
# # #<Pet id: nil, name: "Spook">,
-
# # #<Pet id: nil, name: "Choo-Choo">
-
# # ]
-
#
-
# person.pets.select([:id, :name])
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy">,
-
# # #<Pet id: 2, name: "Spook">,
-
# # #<Pet id: 3, name: "Choo-Choo">
-
# # ]
-
#
-
# Be careful because this also means you’re initializing a model
-
# object with only the fields that you’ve selected. If you attempt
-
# to access a field that is not in the initialized record you’ll
-
# receive:
-
#
-
# person.pets.select(:name).first.person_id
-
# # => ActiveModel::MissingAttributeError: missing attribute: person_id
-
#
-
# *Second:* You can pass a block so it can be used just like Array#select.
-
# This build an array of objects from the database for the scope,
-
# converting them into an array and iterating through them using
-
# Array#select.
-
#
-
# person.pets.select { |pet| pet.name =~ /oo/ }
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.select(:name) { |pet| pet.name =~ /oo/ }
-
# # => [
-
# # #<Pet id: 2, name: "Spook">,
-
# # #<Pet id: 3, name: "Choo-Choo">
-
# # ]
-
1
def select(select = nil, &block)
-
@association.select(select, &block)
-
end
-
-
# Finds an object in the collection responding to the +id+. Uses the same
-
# rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
-
# error if the object can not be found.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
# person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
-
#
-
# person.pets.find(2) { |pet| pet.name.downcase! }
-
# # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
-
#
-
# person.pets.find(2, 3)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def find(*args, &block)
-
@association.find(*args, &block)
-
end
-
-
# Returns the first record, or the first +n+ records, from the collection.
-
# If the collection is empty, the first form returns +nil+, and the second
-
# form returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.first(2)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>
-
# # ]
-
#
-
# another_person_without.pets # => []
-
# another_person_without.pets.first # => nil
-
# another_person_without.pets.first(3) # => []
-
1
def first(*args)
-
@association.first(*args)
-
end
-
-
# Returns the last record, or the last +n+ records, from the collection.
-
# If the collection is empty, the first form returns +nil+, and the second
-
# form returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
#
-
# person.pets.last(2)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# another_person_without.pets # => []
-
# another_person_without.pets.last # => nil
-
# another_person_without.pets.last(3) # => []
-
1
def last(*args)
-
@association.last(*args)
-
end
-
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+ and linked to this object, but have not yet been saved.
-
# You can pass an array of attributes hashes, this will return an array
-
# with the new objects.
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# person.pets.build
-
# # => #<Pet id: nil, name: nil, person_id: 1>
-
#
-
# person.pets.build(name: 'Fancy-Fancy')
-
# # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
-
# # => [
-
# # #<Pet id: nil, name: "Spook", person_id: 1>,
-
# # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
-
# # #<Pet id: nil, name: "Brain", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 5 # size of the collection
-
# person.pets.count # => 0 # count from database
-
1
def build(attributes = {}, &block)
-
@association.build(attributes, &block)
-
end
-
-
# Returns a new object of the collection type that has been instantiated with
-
# attributes, linked to this object and that has already been saved (if it
-
# passes the validations).
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# person.pets.create(name: 'Fancy-Fancy')
-
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 3
-
# person.pets.count # => 3
-
#
-
# person.pets.find(1, 2, 3)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def create(attributes = {}, &block)
-
@association.create(attributes, &block)
-
end
-
-
# Like +create+, except that if the record is invalid, raises an exception.
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# class Pet
-
# validates :name, presence: true
-
# end
-
#
-
# person.pets.create!(name: nil)
-
# # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
-
1
def create!(attributes = {}, &block)
-
@association.create!(attributes, &block)
-
end
-
-
# Add one or more records to the collection by setting their foreign keys
-
# to the association's primary key. Since << flattens its argument list and
-
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
-
# so method calls may be chained.
-
#
-
# class Person < ActiveRecord::Base
-
# pets :has_many
-
# end
-
#
-
# person.pets.size # => 0
-
# person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
-
# person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
-
# person.pets.size # => 3
-
#
-
# person.id # => 1
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
-
# person.pets.size # => 5
-
1
def concat(*records)
-
@association.concat(*records)
-
end
-
-
# Replace this collection with +other_array+. This will perform a diff
-
# and delete/add only records that have changed.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
-
#
-
# other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
-
#
-
# person.pets.replace(other_pets)
-
#
-
# person.pets
-
# # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
-
#
-
# If the supplied array has an incorrect association type, it raises
-
# an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
-
#
-
# person.pets.replace(["doo", "ggie", "gaga"])
-
# # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
-
1
def replace(other_array)
-
@association.replace(other_array)
-
end
-
-
# Deletes all the records from the collection. For +has_many+ associations,
-
# the deletion is done according to the strategy specified by the <tt>:dependent</tt>
-
# option. Returns an array with the deleted records.
-
#
-
# If no <tt>:dependent</tt> option is given, then it will follow the
-
# default strategy. The default strategy is <tt>:nullify</tt>. This
-
# sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>,
-
# the default strategy is +delete_all+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets # dependent: :nullify option by default
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1, 2, 3)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
-
# # #<Pet id: 2, name: "Spook", person_id: nil>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
-
# # ]
-
#
-
# If it is set to <tt>:destroy</tt> all the objects from the collection
-
# are removed by calling their +destroy+ method. See +destroy+ for more
-
# information.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :destroy
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1, 2, 3)
-
# # => ActiveRecord::RecordNotFound
-
#
-
# If it is set to <tt>:delete_all</tt>, all the objects are deleted
-
# *without* calling their +destroy+ method.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :delete_all
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1, 2, 3)
-
# # => ActiveRecord::RecordNotFound
-
1
def delete_all
-
@association.delete_all
-
end
-
-
# Deletes the records of the collection directly from the database.
-
# This will _always_ remove the records ignoring the +:dependent+
-
# option.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy_all
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1) # => Couldn't find Pet with id=1
-
1
def destroy_all
-
@association.destroy_all
-
end
-
-
# Deletes the +records+ supplied and removes them from the collection. For
-
# +has_many+ associations, the deletion is done according to the strategy
-
# specified by the <tt>:dependent</tt> option. Returns an array with the
-
# deleted records.
-
#
-
# If no <tt>:dependent</tt> option is given, then it will follow the default
-
# strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
-
# keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
-
# strategy is +delete_all+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets # dependent: :nullify option by default
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1)
-
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
-
#
-
# If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
-
# their +destroy+ method. See +destroy+ for more information.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :destroy
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1), Pet.find(3))
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 1
-
# person.pets
-
# # => [#<Pet id: 2, name: "Spook", person_id: 1>]
-
#
-
# Pet.find(1, 3)
-
# # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
-
#
-
# If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
-
# *without* calling their +destroy+ method.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :delete_all
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1)
-
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
-
#
-
# You can pass +Fixnum+ or +String+ values, it finds the records
-
# responding to the +id+ and executes delete on them.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete("1")
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.delete(2, 3)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def delete(*records)
-
@association.delete(*records)
-
end
-
-
# Destroys the +records+ supplied and removes them from the collection.
-
# This method will _always_ remove record from the database ignoring
-
# the +:dependent+ option. Returns an array with the removed records.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(Pet.find(2), Pet.find(3))
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
-
#
-
# You can pass +Fixnum+ or +String+ values, it finds the records
-
# responding to the +id+ and then deletes them from the database.
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy("4")
-
# # => #<Pet id: 4, name: "Benny", person_id: 1>
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(5, 6)
-
# # => [
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
-
1
def destroy(*records)
-
@association.destroy(*records)
-
end
-
-
# Specifies whether the records should be unique or not.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.select(:name)
-
# # => [
-
# # #<Pet name: "Fancy-Fancy">,
-
# # #<Pet name: "Fancy-Fancy">
-
# # ]
-
#
-
# person.pets.select(:name).uniq
-
# # => [#<Pet name: "Fancy-Fancy">]
-
1
def uniq
-
@association.uniq
-
end
-
-
# Count all records using SQL.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def count(column_name = nil, options = {})
-
@association.count(column_name, options)
-
end
-
-
# Returns the size of the collection. If the collection hasn't been loaded,
-
# it executes a <tt>SELECT COUNT(*)</tt> query.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
-
#
-
# person.pets # This will execute a SELECT * FROM query
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 3
-
# # Because the collection is already loaded, this will behave like
-
# # collection.size and no SQL count query is executed.
-
1
def size
-
@association.size
-
end
-
-
# Returns the size of the collection calling +size+ on the target.
-
# If the collection has been already loaded, +length+ and +size+ are
-
# equivalent.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.length # => 3
-
# # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
-
#
-
# # Because the collection is loaded, you can
-
# # call the collection with no additional queries:
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def length
-
@association.length
-
end
-
-
# Returns +true+ if the collection is empty.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 1
-
# person.pets.empty? # => false
-
#
-
# person.pets.delete_all
-
#
-
# person.pets.count # => 0
-
# person.pets.empty? # => true
-
1
def empty?
-
@association.empty?
-
end
-
-
# Returns +true+ if the collection is not empty.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 0
-
# person.pets.any? # => false
-
#
-
# person.pets << Pet.new(name: 'Snoop')
-
# person.pets.count # => 0
-
# person.pets.any? # => true
-
#
-
# You can also pass a block to define criteria. The behaviour
-
# is the same, it returns true if the collection based on the
-
# criteria is not empty.
-
#
-
# person.pets
-
# # => [#<Pet name: "Snoop", group: "dogs">]
-
#
-
# person.pets.any? do |pet|
-
# pet.group == 'cats'
-
# end
-
# # => false
-
#
-
# person.pets.any? do |pet|
-
# pet.group == 'dogs'
-
# end
-
# # => true
-
1
def any?(&block)
-
@association.any?(&block)
-
end
-
-
# Returns true if the collection has more than one record.
-
# Equivalent to <tt>collection.size > 1</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count #=> 1
-
# person.pets.many? #=> false
-
#
-
# person.pets << Pet.new(name: 'Snoopy')
-
# person.pets.count #=> 2
-
# person.pets.many? #=> true
-
#
-
# You can also pass a block to define criteria. The
-
# behaviour is the same, it returns true if the collection
-
# based on the criteria has more than one record.
-
#
-
# person.pets
-
# # => [
-
# # #<Pet name: "Gorby", group: "cats">,
-
# # #<Pet name: "Puff", group: "cats">,
-
# # #<Pet name: "Snoop", group: "dogs">
-
# # ]
-
#
-
# person.pets.many? do |pet|
-
# pet.group == 'dogs'
-
# end
-
# # => false
-
#
-
# person.pets.many? do |pet|
-
# pet.group == 'cats'
-
# end
-
# # => true
-
1
def many?(&block)
-
@association.many?(&block)
-
end
-
-
# Returns +true+ if the given object is present in the collection.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # => [#<Pet id: 20, name: "Snoop">]
-
#
-
# person.pets.include?(Pet.find(20)) # => true
-
# person.pets.include?(Pet.find(21)) # => false
-
1
def include?(record)
-
@association.include?(record)
-
end
-
-
1
alias_method :new, :build
-
-
1
def proxy_association
-
@association
-
end
-
-
# We don't want this object to be put on the scoping stack, because
-
# that could create an infinite loop where we call an @association
-
# method, which gets the current scope, which is this object, which
-
# delegates to @association, and so on.
-
1
def scoping
-
@association.scope.scoping { yield }
-
end
-
-
# Returns a <tt>Relation</tt> object for the records in this association
-
1
def scope
-
association = @association
-
-
@association.scope.extending! do
-
define_method(:proxy_association) { association }
-
end
-
end
-
-
# :nodoc:
-
1
alias spawn scope
-
-
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
-
# contain the same number of elements and if each element is equal
-
# to the corresponding element in the other array, otherwise returns
-
# +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>
-
# # ]
-
#
-
# other = person.pets.to_ary
-
#
-
# person.pets == other
-
# # => true
-
#
-
# other = [Pet.new(id: 1), Pet.new(id: 2)]
-
#
-
# person.pets == other
-
# # => false
-
1
def ==(other)
-
load_target == other
-
end
-
-
# Returns a new array of objects from the collection. If the collection
-
# hasn't been loaded, it fetches the records from the database.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# other_pets = person.pets.to_ary
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# other_pets.replace([Pet.new(name: 'BooGoo')])
-
#
-
# other_pets
-
# # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
-
#
-
# person.pets
-
# # This is not affected by replace
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
1
def to_ary
-
load_target.dup
-
end
-
1
alias_method :to_a, :to_ary
-
-
# Adds one or more +records+ to the collection by setting their foreign keys
-
# to the association‘s primary key. Returns +self+, so several appends may be
-
# chained together.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 0
-
# person.pets << Pet.new(name: 'Fancy-Fancy')
-
# person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
-
# person.pets.size # => 3
-
#
-
# person.id # => 1
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def <<(*records)
-
proxy_association.concat(records) && self
-
end
-
1
alias_method :push, :<<
-
-
# Equivalent to +delete_all+. The difference is that returns +self+, instead
-
# of an array with the deleted objects, so methods can be chained. See
-
# +delete_all+ for more information.
-
1
def clear
-
delete_all
-
self
-
end
-
-
# Reloads the collection from the database. Returns +self+.
-
# Equivalent to <tt>collection(true)</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets # uses the pets cache
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets.reload # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets(true) # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
1
def reload
-
proxy_association.reload
-
self
-
end
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module AttributeAssignment
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::DeprecatedMassAssignmentSecurity
-
1
include ActiveModel::ForbiddenAttributesProtection
-
-
# Allows you to set all the attributes by passing in a hash of attributes with
-
# keys matching the attribute names (which again matches the column names).
-
#
-
# If the passed hash responds to <tt>permitted?</tt> method and the return value
-
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
-
# exception is raised.
-
1
def assign_attributes(new_attributes)
-
return if new_attributes.blank?
-
-
attributes = new_attributes.stringify_keys
-
multi_parameter_attributes = []
-
nested_parameter_attributes = []
-
-
attributes = sanitize_for_mass_assignment(attributes)
-
-
attributes.each do |k, v|
-
if k.include?("(")
-
multi_parameter_attributes << [ k, v ]
-
elsif v.is_a?(Hash)
-
nested_parameter_attributes << [ k, v ]
-
else
-
_assign_attribute(k, v)
-
end
-
end
-
-
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
-
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
-
end
-
-
1
alias attributes= assign_attributes
-
-
1
private
-
-
1
def _assign_attribute(k, v)
-
public_send("#{k}=", v)
-
rescue NoMethodError
-
if respond_to?("#{k}=")
-
raise
-
else
-
raise UnknownAttributeError, "unknown attribute: #{k}"
-
end
-
end
-
-
# Assign any deferred nested attributes after the base attributes have been set.
-
1
def assign_nested_parameter_attributes(pairs)
-
pairs.each { |k, v| _assign_attribute(k, v) }
-
end
-
-
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
-
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
-
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
-
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
-
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
-
1
def assign_multiparameter_attributes(pairs)
-
execute_callstack_for_multiparameter_attributes(
-
extract_callstack_for_multiparameter_attributes(pairs)
-
)
-
end
-
-
1
def execute_callstack_for_multiparameter_attributes(callstack)
-
errors = []
-
callstack.each do |name, values_with_empty_parameters|
-
begin
-
send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
-
rescue => ex
-
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
-
end
-
end
-
unless errors.empty?
-
error_descriptions = errors.map { |ex| ex.message }.join(",")
-
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
-
end
-
end
-
-
1
def extract_callstack_for_multiparameter_attributes(pairs)
-
attributes = { }
-
-
pairs.each do |(multiparameter_name, value)|
-
attribute_name = multiparameter_name.split("(").first
-
attributes[attribute_name] ||= {}
-
-
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
-
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
-
end
-
-
attributes
-
end
-
-
1
def type_cast_attribute_value(multiparameter_name, value)
-
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
-
end
-
-
1
def find_parameter_position(multiparameter_name)
-
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
-
end
-
-
1
class MultiparameterAttribute #:nodoc:
-
1
attr_reader :object, :name, :values, :column
-
-
1
def initialize(object, name, values)
-
@object = object
-
@name = name
-
@values = values
-
end
-
-
1
def read_value
-
return if values.values.compact.empty?
-
-
@column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
-
klass = column.klass
-
-
if klass == Time
-
read_time
-
elsif klass == Date
-
read_date
-
else
-
read_other(klass)
-
end
-
end
-
-
1
private
-
-
1
def instantiate_time_object(set_values)
-
if object.class.send(:create_time_zone_conversion_attribute?, name, column)
-
Time.zone.local(*set_values)
-
else
-
Time.time_with_datetime_fallback(object.class.default_timezone, *set_values)
-
end
-
end
-
-
1
def read_time
-
# If column is a :time (and not :date or :timestamp) there is no need to validate if
-
# there are year/month/day fields
-
if column.type == :time
-
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
-
{ 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
-
values[key] ||= value
-
end
-
else
-
# else column is a timestamp, so if Date bits were not provided, error
-
validate_missing_parameters!([1,2,3])
-
-
# If Date bits were provided but blank, then return nil
-
return if blank_date_parameter?
-
end
-
-
max_position = extract_max_param(6)
-
set_values = values.values_at(*(1..max_position))
-
# If Time bits are not there, then default to 0
-
(3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
-
instantiate_time_object(set_values)
-
end
-
-
1
def read_date
-
return if blank_date_parameter?
-
set_values = values.values_at(1,2,3)
-
begin
-
Date.new(*set_values)
-
rescue ArgumentError # if Date.new raises an exception on an invalid date
-
instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
-
end
-
end
-
-
1
def read_other(klass)
-
max_position = extract_max_param
-
positions = (1..max_position)
-
validate_missing_parameters!(positions)
-
-
set_values = values.values_at(*positions)
-
klass.new(*set_values)
-
end
-
-
# Checks whether some blank date parameter exists. Note that this is different
-
# than the validate_missing_parameters! method, since it just checks for blank
-
# positions instead of missing ones, and does not raise in case one blank position
-
# exists. The caller is responsible to handle the case of this returning true.
-
1
def blank_date_parameter?
-
(1..3).any? { |position| values[position].blank? }
-
end
-
-
# If some position is not provided, it errors out a missing parameter exception.
-
1
def validate_missing_parameters!(positions)
-
if missing_parameter = positions.detect { |position| !values.key?(position) }
-
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
-
end
-
end
-
-
1
def extract_max_param(upper_cap = 100)
-
[values.keys.max, upper_cap].min
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/enumerable'
-
-
1
module ActiveRecord
-
# = Active Record Attribute Methods
-
1
module AttributeMethods
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::AttributeMethods
-
-
1
included do
-
1
include Read
-
1
include Write
-
1
include BeforeTypeCast
-
1
include Query
-
1
include PrimaryKey
-
1
include TimeZoneConversion
-
1
include Dirty
-
1
include Serialization
-
end
-
-
1
module ClassMethods
-
# Generates all the attribute related methods for columns in the database
-
# accessors, mutators and query methods.
-
1
def define_attribute_methods # :nodoc:
-
# Use a mutex; we don't want two thread simaltaneously trying to define
-
# attribute methods.
-
@attribute_methods_mutex.synchronize do
-
return if attribute_methods_generated?
-
superclass.define_attribute_methods unless self == base_class
-
super(column_names)
-
@attribute_methods_generated = true
-
end
-
end
-
-
1
def attribute_methods_generated? # :nodoc:
-
5
@attribute_methods_generated ||= false
-
end
-
-
1
def undefine_attribute_methods # :nodoc:
-
5
super if attribute_methods_generated?
-
5
@attribute_methods_generated = false
-
end
-
-
# Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
-
# \Active \Record method is defined in the model, otherwise +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# def save
-
# 'already defined by Active Record'
-
# end
-
# end
-
#
-
# Person.instance_method_already_implemented?(:save)
-
# # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
-
#
-
# Person.instance_method_already_implemented?(:name)
-
# # => false
-
1
def instance_method_already_implemented?(method_name)
-
if dangerous_attribute_method?(method_name)
-
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
-
end
-
-
if superclass == Base
-
super
-
else
-
# If B < A and A defines its own attribute method, then we don't want to overwrite that.
-
defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
-
defined && !ActiveRecord::Base.method_defined?(method_name) || super
-
end
-
end
-
-
# A method name is 'dangerous' if it is already defined by Active Record, but
-
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
-
1
def dangerous_attribute_method?(name) # :nodoc:
-
method_defined_within?(name, Base)
-
end
-
-
1
def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
-
if klass.method_defined?(name) || klass.private_method_defined?(name)
-
if sup.method_defined?(name) || sup.private_method_defined?(name)
-
klass.instance_method(name).owner != sup.instance_method(name).owner
-
else
-
true
-
end
-
else
-
false
-
end
-
end
-
-
# Returns +true+ if +attribute+ is an attribute method and table exists,
-
# +false+ otherwise.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# Person.attribute_method?('name') # => true
-
# Person.attribute_method?(:age=) # => true
-
# Person.attribute_method?(:nothing) # => false
-
1
def attribute_method?(attribute)
-
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
-
end
-
-
# Returns an array of column names as strings if it's not an abstract class and
-
# table exists. Otherwise it returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# Person.attribute_names
-
# # => ["id", "created_at", "updated_at", "name", "age"]
-
1
def attribute_names
-
@attribute_names ||= if !abstract_class? && table_exists?
-
column_names
-
else
-
[]
-
end
-
end
-
end
-
-
# If we haven't generated any methods yet, generate them, then
-
# see if we've created the method we're looking for.
-
1
def method_missing(method, *args, &block) # :nodoc:
-
unless self.class.attribute_methods_generated?
-
self.class.define_attribute_methods
-
-
if respond_to_without_attributes?(method)
-
send(method, *args, &block)
-
else
-
super
-
end
-
else
-
super
-
end
-
end
-
-
1
def attribute_missing(match, *args, &block) # :nodoc:
-
if self.class.columns_hash[match.attr_name]
-
ActiveSupport::Deprecation.warn(
-
"The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
-
"dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
-
"is a column of the table. If this error has happened through normal usage of Active " \
-
"Record (rather than through your own code or external libraries), please report it as " \
-
"a bug."
-
)
-
end
-
-
super
-
end
-
-
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
-
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
-
# which will all return +true+. It also define the attribute methods if they have
-
# not been generated.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.new
-
# person.respond_to(:name) # => true
-
# person.respond_to(:name=) # => true
-
# person.respond_to(:name?) # => true
-
# person.respond_to('age') # => true
-
# person.respond_to('age=') # => true
-
# person.respond_to('age?') # => true
-
# person.respond_to(:nothing) # => false
-
1
def respond_to?(name, include_private = false)
-
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
-
super
-
end
-
-
# Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.new
-
# person.has_attribute?(:name) # => true
-
# person.has_attribute?('age') # => true
-
# person.has_attribute?(:nothing) # => false
-
1
def has_attribute?(attr_name)
-
@attributes.has_key?(attr_name.to_s)
-
end
-
-
# Returns an array of names for the attributes available on this object.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.new
-
# person.attribute_names
-
# # => ["id", "created_at", "updated_at", "name", "age"]
-
1
def attribute_names
-
@attributes.keys
-
end
-
-
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.create(name: 'Francesco', age: 22)
-
# person.attributes
-
# # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
-
1
def attributes
-
attribute_names.each_with_object({}) { |name, attrs|
-
attrs[name] = read_attribute(name)
-
}
-
end
-
-
# Returns an <tt>#inspect</tt>-like string for the value of the
-
# attribute +attr_name+. String attributes are truncated upto 50
-
# characters, and Date and Time attributes are returned in the
-
# <tt>:db</tt> format. Other attributes return the value of
-
# <tt>#inspect</tt> without modification.
-
#
-
# person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
-
#
-
# person.attribute_for_inspect(:name)
-
# # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\""
-
#
-
# person.attribute_for_inspect(:created_at)
-
# # => "\"2012-10-22 00:15:07\""
-
1
def attribute_for_inspect(attr_name)
-
value = read_attribute(attr_name)
-
-
if value.is_a?(String) && value.length > 50
-
"#{value[0..50]}...".inspect
-
elsif value.is_a?(Date) || value.is_a?(Time)
-
%("#{value.to_s(:db)}")
-
else
-
value.inspect
-
end
-
end
-
-
# Returns +true+ if the specified +attribute+ has been set by the user or by a
-
# database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
-
# to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
-
# Note that it always returns +true+ with boolean attributes.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# person = Task.new(title: '', is_done: false)
-
# person.attribute_present?(:title) # => false
-
# person.attribute_present?(:is_done) # => true
-
# person.name = 'Francesco'
-
# person.is_done = true
-
# person.attribute_present?(:title) # => true
-
# person.attribute_present?(:is_done) # => true
-
1
def attribute_present?(attribute)
-
value = read_attribute(attribute)
-
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
-
end
-
-
# Returns the column object for the named attribute. Returns +nil+ if the
-
# named attribute not exists.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.new
-
# person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
-
# # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
-
#
-
# person.column_for_attribute(:nothing)
-
# # => nil
-
1
def column_for_attribute(name)
-
# FIXME: should this return a null object for columns that don't exist?
-
self.class.columns_hash[name.to_s]
-
end
-
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
-
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). It raises
-
# <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
-
#
-
# Alias for the <tt>read_attribute</tt> method.
-
#
-
# class Person < ActiveRecord::Base
-
# belongs_to :organization
-
# end
-
#
-
# person = Person.new(name: 'Francesco', age: '22')
-
# person[:name] # => "Francesco"
-
# person[:age] # => 22
-
#
-
# person = Person.select('id').first
-
# person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
-
# person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
-
1
def [](attr_name)
-
read_attribute(attr_name) { |n| missing_attribute(n, caller) }
-
end
-
-
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
-
# (Alias for the protected <tt>write_attribute</tt> method).
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.new
-
# person[:age] = '22'
-
# person[:age] # => 22
-
# person[:age] # => Fixnum
-
1
def []=(attr_name, value)
-
write_attribute(attr_name, value)
-
end
-
-
1
protected
-
-
1
def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
-
attribute_names.each do |name|
-
attributes[name] = clone_attribute_value(reader_method, name)
-
end
-
attributes
-
end
-
-
1
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
-
value = send(reader_method, attribute_name)
-
value.duplicable? ? value.clone : value
-
rescue TypeError, NoMethodError
-
value
-
end
-
-
1
def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
-
arel_attributes_with_values(attributes_for_create(attribute_names))
-
end
-
-
1
def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
-
arel_attributes_with_values(attributes_for_update(attribute_names))
-
end
-
-
1
def attribute_method?(attr_name) # :nodoc:
-
defined?(@attributes) && @attributes.include?(attr_name)
-
end
-
-
1
private
-
-
# Returns a Hash of the Arel::Attributes and attribute values that have been
-
# type casted for use in an Arel insert/update method.
-
1
def arel_attributes_with_values(attribute_names)
-
attrs = {}
-
arel_table = self.class.arel_table
-
-
attribute_names.each do |name|
-
attrs[arel_table[name]] = typecasted_attribute_value(name)
-
end
-
attrs
-
end
-
-
# Filters the primary keys and readonly attributes from the attribute names.
-
1
def attributes_for_update(attribute_names)
-
attribute_names.select do |name|
-
column_for_attribute(name) && !pk_attribute?(name) && !readonly_attribute?(name)
-
end
-
end
-
-
# Filters out the primary keys, from the attribute names, when the primary
-
# key is to be generated (e.g. the id attribute has no value).
-
1
def attributes_for_create(attribute_names)
-
attribute_names.select do |name|
-
column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
-
end
-
end
-
-
1
def readonly_attribute?(name)
-
self.class.readonly_attributes.include?(name)
-
end
-
-
1
def pk_attribute?(name)
-
column_for_attribute(name).primary
-
end
-
-
1
def typecasted_attribute_value(name)
-
# FIXME: we need @attributes to be used consistently.
-
# If the values stored in @attributes were already typecasted, this code
-
# could be simplified
-
read_attribute(name)
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
# = Active Record Attribute Methods Before Type Cast
-
#
-
# <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> provides a way to
-
# read the value of the attributes before typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(id: '1', completed_on: '2012-10-21')
-
# task.id # => 1
-
# task.completed_on # => Sun, 21 Oct 2012
-
#
-
# task.attributes_before_type_cast
-
# # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
-
# task.read_attribute_before_type_cast('id') # => "1"
-
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
-
#
-
# In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
-
# it declares a method for all attributes with the <tt>*_before_type_cast</tt>
-
# suffix.
-
#
-
# task.id_before_type_cast # => "1"
-
# task.completed_on_before_type_cast # => "2012-10-21"
-
1
module BeforeTypeCast
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
attribute_method_suffix "_before_type_cast"
-
end
-
-
# Returns the value of the attribute identified by +attr_name+ before
-
# typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(id: '1', completed_on: '2012-10-21')
-
# task.read_attribute('id') # => 1
-
# task.read_attribute_before_type_cast('id') # => '1'
-
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
-
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
-
1
def read_attribute_before_type_cast(attr_name)
-
@attributes[attr_name]
-
end
-
-
# Returns a hash of attributes before typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
-
# task.attributes
-
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
-
# task.attributes_before_type_cast
-
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
-
1
def attributes_before_type_cast
-
@attributes
-
end
-
-
1
private
-
-
# Handle *_before_type_cast for method_missing.
-
1
def attribute_before_type_cast(attribute_name)
-
read_attribute_before_type_cast(attribute_name)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Dirty # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
include ActiveModel::Dirty
-
-
1
included do
-
1
if self < ::ActiveRecord::Timestamp
-
raise "You cannot include Dirty after Timestamp"
-
end
-
-
1
class_attribute :partial_writes, instance_writer: false
-
1
self.partial_writes = true
-
-
1
def self.partial_updates=(v); self.partial_writes = v; end
-
1
def self.partial_updates?; partial_writes?; end
-
1
def self.partial_updates; partial_writes; end
-
-
1
ActiveSupport::Deprecation.deprecate_methods(
-
singleton_class,
-
:partial_updates= => :partial_writes=,
-
:partial_updates? => :partial_writes?,
-
:partial_updates => :partial_writes
-
)
-
end
-
-
# Attempts to +save+ the record and clears changed attributes if successful.
-
1
def save(*)
-
if status = super
-
@previously_changed = changes
-
@changed_attributes.clear
-
end
-
status
-
end
-
-
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
-
1
def save!(*)
-
super.tap do
-
@previously_changed = changes
-
@changed_attributes.clear
-
end
-
end
-
-
# <tt>reload</tt> the record and clears changed attributes.
-
1
def reload(*)
-
super.tap do
-
@previously_changed.clear
-
@changed_attributes.clear
-
end
-
end
-
-
1
private
-
# Wrap write_attribute to remember original attribute value.
-
1
def write_attribute(attr, value)
-
attr = attr.to_s
-
-
# The attribute already has an unsaved change.
-
if attribute_changed?(attr)
-
old = @changed_attributes[attr]
-
@changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
-
else
-
old = clone_attribute_value(:read_attribute, attr)
-
@changed_attributes[attr] = old if _field_changed?(attr, old, value)
-
end
-
-
# Carry on.
-
super(attr, value)
-
end
-
-
1
def update(*)
-
partial_writes? ? super(keys_for_partial_write) : super
-
end
-
-
1
def create(*)
-
partial_writes? ? super(keys_for_partial_write) : super
-
end
-
-
# Serialized attributes should always be written in case they've been
-
# changed in place.
-
1
def keys_for_partial_write
-
changed | (attributes.keys & self.class.serialized_attributes.keys)
-
end
-
-
1
def _field_changed?(attr, old, value)
-
if column = column_for_attribute(attr)
-
if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
-
changes_from_zero_to_string?(old, value))
-
value = nil
-
else
-
value = column.type_cast(value)
-
end
-
end
-
-
old != value
-
end
-
-
1
def changes_from_nil_to_empty_string?(column, old, value)
-
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
-
# Hence we don't record it as a change if the value changes from nil to ''.
-
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
-
# be typecast back to 0 (''.to_i => 0)
-
column.null && (old.nil? || old == 0) && value.blank?
-
end
-
-
1
def changes_from_zero_to_string?(old, value)
-
# For columns with old 0 and value non-empty string
-
old == 0 && value.is_a?(String) && value.present? && value != '0'
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Query
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
attribute_method_suffix "?"
-
end
-
-
1
def query_attribute(attr_name)
-
value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
-
-
case value
-
when true then true
-
when false, nil then false
-
else
-
column = self.class.columns_hash[attr_name]
-
if column.nil?
-
if Numeric === value || value !~ /[^0-9]/
-
!value.to_i.zero?
-
else
-
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
-
!value.blank?
-
end
-
elsif column.number?
-
!value.zero?
-
else
-
!value.blank?
-
end
-
end
-
end
-
-
1
private
-
# Handle *? for method_missing.
-
1
def attribute?(attribute_name)
-
query_attribute(attribute_name)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Read
-
1
extend ActiveSupport::Concern
-
-
1
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
-
-
1
included do
-
1
class_attribute :attribute_types_cached_by_default, instance_writer: false
-
1
self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
-
end
-
-
1
module ClassMethods
-
# +cache_attributes+ allows you to declare which converted attribute
-
# values should be cached. Usually caching only pays off for attributes
-
# with expensive conversion methods, like time related columns (e.g.
-
# +created_at+, +updated_at+).
-
1
def cache_attributes(*attribute_names)
-
cached_attributes.merge attribute_names.map { |attr| attr.to_s }
-
end
-
-
# Returns the attributes which are cached. By default time related columns
-
# with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
-
1
def cached_attributes
-
@cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
-
end
-
-
# Returns +true+ if the provided attribute is being cached.
-
1
def cache_attribute?(attr_name)
-
cached_attributes.include?(attr_name)
-
end
-
-
1
protected
-
-
# We want to generate the methods via module_eval rather than define_method,
-
# because define_method is slower on dispatch and uses more memory (because it
-
# creates a closure).
-
#
-
# But sometimes the database might return columns with characters that are not
-
# allowed in normal method names (like 'my_column(omg)'. So to work around this
-
# we first define with the __temp__ identifier, and then use alias method to
-
# rename it to what we want.
-
1
def define_method_attribute(attr_name)
-
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
-
def __temp__
-
read_attribute('#{attr_name}') { |n| missing_attribute(n, caller) }
-
end
-
alias_method '#{attr_name}', :__temp__
-
undef_method :__temp__
-
STR
-
end
-
-
1
private
-
-
1
def cacheable_column?(column)
-
if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
-
! serialized_attributes.include? column.name
-
else
-
attribute_types_cached_by_default.include?(column.type)
-
end
-
end
-
end
-
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after
-
# it has been typecast (for example, "2004-12-12" in a data column is cast
-
# to a date object, like Date.new(2004, 12, 12)).
-
1
def read_attribute(attr_name)
-
# If it's cached, just return it
-
# We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/3552829.
-
name = attr_name.to_s
-
@attributes_cache[name] || @attributes_cache.fetch(name) {
-
column = @columns_hash.fetch(name) {
-
return @attributes.fetch(name) {
-
if name == 'id' && self.class.primary_key != name
-
read_attribute(self.class.primary_key)
-
end
-
}
-
}
-
-
value = @attributes.fetch(name) {
-
return block_given? ? yield(name) : nil
-
}
-
-
if self.class.cache_attribute?(name)
-
@attributes_cache[name] = column.type_cast(value)
-
else
-
column.type_cast value
-
end
-
}
-
end
-
-
1
private
-
-
1
def attribute(attribute_name)
-
read_attribute(attribute_name)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Serialization
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
# Returns a hash of all the attributes that have been specified for
-
# serialization as keys and their class restriction as values.
-
1
class_attribute :serialized_attributes, instance_accessor: false
-
1
self.serialized_attributes = {}
-
end
-
-
1
module ClassMethods
-
# If you have an attribute that needs to be saved to the database as an
-
# object, and retrieved as the same object, then specify the name of that
-
# attribute using this method and it will be handled automatically. The
-
# serialization is done through YAML. If +class_name+ is specified, the
-
# serialized object must be of that class on retrieval or
-
# <tt>SerializationTypeMismatch</tt> will be raised.
-
#
-
# ==== Parameters
-
#
-
# * +attr_name+ - The field name that should be serialized.
-
# * +class_name+ - Optional, class name that the object type should be equal to.
-
#
-
# ==== Example
-
#
-
# # Serialize a preferences attribute.
-
# class User < ActiveRecord::Base
-
# serialize :preferences
-
# end
-
1
def serialize(attr_name, class_name = Object)
-
include Behavior
-
-
coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
-
class_name
-
else
-
Coders::YAMLColumn.new(class_name)
-
end
-
-
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
-
# has its own hash of own serialized attributes
-
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
-
end
-
end
-
-
1
def serialized_attributes
-
message = "Instance level serialized_attributes method is deprecated, please use class level method."
-
ActiveSupport::Deprecation.warn message
-
defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
-
end
-
-
1
class Type # :nodoc:
-
1
def initialize(column)
-
@column = column
-
end
-
-
1
def type_cast(value)
-
value.unserialized_value
-
end
-
-
1
def type
-
@column.type
-
end
-
end
-
-
1
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
-
1
def unserialized_value
-
state == :serialized ? unserialize : value
-
end
-
-
1
def serialized_value
-
state == :unserialized ? serialize : value
-
end
-
-
1
def unserialize
-
self.state = :unserialized
-
self.value = coder.load(value)
-
end
-
-
1
def serialize
-
self.state = :serialized
-
self.value = coder.dump(value)
-
end
-
end
-
-
# This is only added to the model when serialize is called, which
-
# ensures we do not make things slower when serialization is not used.
-
1
module Behavior #:nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
def initialize_attributes(attributes, options = {})
-
serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
-
super(attributes, options)
-
-
serialized_attributes.each do |key, coder|
-
if attributes.key?(key)
-
attributes[key] = Attribute.new(coder, attributes[key], serialized)
-
end
-
end
-
-
attributes
-
end
-
end
-
-
1
def type_cast_attribute_for_write(column, value)
-
if column && coder = self.class.serialized_attributes[column.name]
-
Attribute.new(coder, value, :unserialized)
-
else
-
super
-
end
-
end
-
-
1
def read_attribute_before_type_cast(attr_name)
-
if self.class.serialized_attributes.include?(attr_name)
-
super.unserialized_value
-
else
-
super
-
end
-
end
-
-
1
def attributes_before_type_cast
-
super.dup.tap do |attributes|
-
self.class.serialized_attributes.each_key do |key|
-
if attributes.key?(key)
-
attributes[key] = attributes[key].unserialized_value
-
end
-
end
-
end
-
end
-
-
1
def typecasted_attribute_value(name)
-
if self.class.serialized_attributes.include?(name)
-
@attributes[name].serialized_value
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module TimeZoneConversion
-
1
class Type # :nodoc:
-
1
def initialize(column)
-
@column = column
-
end
-
-
1
def type_cast(value)
-
value = @column.type_cast(value)
-
value.acts_like?(:time) ? value.in_time_zone : value
-
end
-
-
1
def type
-
@column.type
-
end
-
end
-
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
mattr_accessor :time_zone_aware_attributes, instance_writer: false
-
1
self.time_zone_aware_attributes = false
-
-
1
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
-
1
self.skip_time_zone_conversion_for_attributes = []
-
end
-
-
1
module ClassMethods
-
1
protected
-
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
-
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
-
1
def define_method_attribute=(attr_name)
-
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
-
method_body, line = <<-EOV, __LINE__ + 1
-
def #{attr_name}=(original_time)
-
time = original_time
-
unless time.acts_like?(:time)
-
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
-
end
-
zoned_time = time && time.in_time_zone rescue nil
-
rounded_time = round_usec(zoned_time)
-
rounded_value = round_usec(read_attribute("#{attr_name}"))
-
if (rounded_value != rounded_time) || (!rounded_value && original_time)
-
write_attribute("#{attr_name}", original_time)
-
#{attr_name}_will_change!
-
@attributes_cache["#{attr_name}"] = zoned_time
-
end
-
end
-
EOV
-
generated_attribute_methods.module_eval(method_body, __FILE__, line)
-
else
-
super
-
end
-
end
-
-
1
private
-
1
def create_time_zone_conversion_attribute?(name, column)
-
time_zone_aware_attributes &&
-
!self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
-
[:datetime, :timestamp].include?(column.type)
-
end
-
end
-
-
1
private
-
1
def round_usec(value)
-
return unless value
-
value.change(:usec => 0)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Write
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
attribute_method_suffix "="
-
end
-
-
1
module ClassMethods
-
1
protected
-
1
def define_method_attribute=(attr_name)
-
if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
-
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
-
else
-
generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
-
write_attribute(attr_name, new_value)
-
end
-
end
-
end
-
end
-
-
# Updates the attribute identified by <tt>attr_name</tt> with the
-
# specified +value+. Empty strings for fixnum and float columns are
-
# turned into +nil+.
-
1
def write_attribute(attr_name, value)
-
attr_name = attr_name.to_s
-
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
-
@attributes_cache.delete(attr_name)
-
column = column_for_attribute(attr_name)
-
-
# If we're dealing with a binary column, write the data to the cache
-
# so we don't attempt to typecast multiple times.
-
if column && column.binary?
-
@attributes_cache[attr_name] = value
-
end
-
-
if column || @attributes.has_key?(attr_name)
-
@attributes[attr_name] = type_cast_attribute_for_write(column, value)
-
else
-
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
-
end
-
end
-
1
alias_method :raw_write_attribute, :write_attribute
-
-
1
private
-
# Handle *= for method_missing.
-
1
def attribute=(attribute_name, value)
-
write_attribute(attribute_name, value)
-
end
-
-
1
def type_cast_attribute_for_write(column, value)
-
return value unless column
-
-
column.type_cast_for_write value
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Autosave Association
-
#
-
# +AutosaveAssociation+ is a module that takes care of automatically saving
-
# associated records when their parent is saved. In addition to saving, it
-
# also destroys any associated records that were marked for destruction.
-
# (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
-
#
-
# Saving of the parent, its associations, and the destruction of marked
-
# associations, all happen inside a transaction. This should never leave the
-
# database in an inconsistent state.
-
#
-
# If validations for any of the associations fail, their error messages will
-
# be applied to the parent.
-
#
-
# Note that it also means that associations marked for destruction won't
-
# be destroyed directly. They will however still be marked for destruction.
-
#
-
# Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
-
# When the <tt>:autosave</tt> option is not present new associations are saved.
-
#
-
# == Validation
-
#
-
# Children records are validated unless <tt>:validate</tt> is +false+.
-
#
-
# == Callbacks
-
#
-
# Association with autosave option defines several callbacks on your
-
# model (before_save, after_create, after_update). Please note that
-
# callbacks are executed in the order they were defined in
-
# model. You should avoid modifying the association content, before
-
# autosave callbacks are executed. Placing your callbacks after
-
# associations is usually a good practice.
-
#
-
# == Examples
-
#
-
# === One-to-one Example
-
#
-
# class Post
-
# has_one :author, :autosave => true
-
# end
-
#
-
# Saving changes to the parent and its associated model can now be performed
-
# automatically _and_ atomically:
-
#
-
# post = Post.find(1)
-
# post.title # => "The current global position of migrating ducks"
-
# post.author.name # => "alloy"
-
#
-
# post.title = "On the migration of ducks"
-
# post.author.name = "Eloy Duran"
-
#
-
# post.save
-
# post.reload
-
# post.title # => "On the migration of ducks"
-
# post.author.name # => "Eloy Duran"
-
#
-
# Destroying an associated model, as part of the parent's save action, is as
-
# simple as marking it for destruction:
-
#
-
# post.author.mark_for_destruction
-
# post.author.marked_for_destruction? # => true
-
#
-
# Note that the model is _not_ yet removed from the database:
-
#
-
# id = post.author.id
-
# Author.find_by_id(id).nil? # => false
-
#
-
# post.save
-
# post.reload.author # => nil
-
#
-
# Now it _is_ removed from the database:
-
#
-
# Author.find_by_id(id).nil? # => true
-
#
-
# === One-to-many Example
-
#
-
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
-
#
-
# class Post
-
# has_many :comments # :autosave option is not declared
-
# end
-
#
-
# post = Post.new(:title => 'ruby rocks')
-
# post.comments.build(:body => 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# post = Post.create(:title => 'ruby rocks')
-
# post.comments.build(:body => 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# post = Post.create(:title => 'ruby rocks')
-
# post.comments.create(:body => 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
-
# are new records or not:
-
#
-
# class Post
-
# has_many :comments, :autosave => true
-
# end
-
#
-
# post = Post.create(:title => 'ruby rocks')
-
# post.comments.create(:body => 'hello world')
-
# post.comments[0].body = 'hi everyone'
-
# post.save # => saves both post and comment, with 'hi everyone' as body
-
#
-
# Destroying one of the associated models as part of the parent's save action
-
# is as simple as marking it for destruction:
-
#
-
# post.comments.last.mark_for_destruction
-
# post.comments.last.marked_for_destruction? # => true
-
# post.comments.length # => 2
-
#
-
# Note that the model is _not_ yet removed from the database:
-
#
-
# id = post.comments.last.id
-
# Comment.find_by_id(id).nil? # => false
-
#
-
# post.save
-
# post.reload.comments.length # => 1
-
#
-
# Now it _is_ removed from the database:
-
#
-
# Comment.find_by_id(id).nil? # => true
-
-
1
module AutosaveAssociation
-
1
extend ActiveSupport::Concern
-
-
1
module AssociationBuilderExtension #:nodoc:
-
1
def build
-
9
model.send(:add_autosave_association_callbacks, reflection)
-
9
super
-
end
-
end
-
-
1
included do
-
1
Associations::Builder::Association.class_eval do
-
1
self.valid_options << :autosave
-
1
include AssociationBuilderExtension
-
end
-
end
-
-
1
module ClassMethods
-
1
private
-
-
1
def define_non_cyclic_method(name, reflection, &block)
-
11
define_method(name) do |*args|
-
result = true; @_already_called ||= {}
-
# Loop prevention for validation of associations
-
unless @_already_called[[name, reflection.name]]
-
begin
-
@_already_called[[name, reflection.name]]=true
-
result = instance_eval(&block)
-
ensure
-
@_already_called[[name, reflection.name]]=false
-
end
-
end
-
-
result
-
end
-
end
-
-
# Adds validation and save callbacks for the association as specified by
-
# the +reflection+.
-
#
-
# For performance reasons, we don't check whether to validate at runtime.
-
# However the validation and callback methods are lazy and those methods
-
# get created when they are invoked for the very first time. However,
-
# this can change, for instance, when using nested attributes, which is
-
# called _after_ the association has been defined. Since we don't want
-
# the callbacks to get defined multiple times, there are guards that
-
# check if the save or validation methods have already been defined
-
# before actually defining them.
-
1
def add_autosave_association_callbacks(reflection)
-
9
save_method = :"autosave_associated_records_for_#{reflection.name}"
-
9
validation_method = :"validate_associated_records_for_#{reflection.name}"
-
9
collection = reflection.collection?
-
-
9
unless method_defined?(save_method)
-
9
if collection
-
5
before_save :before_save_collection_association
-
-
5
define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
-
# Doesn't use after_save as that would save associations added in after_create/after_update twice
-
5
after_create save_method
-
5
after_update save_method
-
elsif reflection.macro == :has_one
-
1
define_method(save_method) { save_has_one_association(reflection) }
-
# Configures two callbacks instead of a single after_save so that
-
# the model may rely on their execution order relative to its
-
# own callbacks.
-
#
-
# For example, given that after_creates run before after_saves, if
-
# we configured instead an after_save there would be no way to fire
-
# a custom after_create callback after the child association gets
-
# created.
-
1
after_create save_method
-
1
after_update save_method
-
else
-
3
define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
-
3
before_save save_method
-
end
-
end
-
-
9
if reflection.validate? && !method_defined?(validation_method)
-
3
method = (collection ? :validate_collection_association : :validate_single_association)
-
3
define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
-
3
validate validation_method
-
end
-
end
-
end
-
-
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
-
1
def reload(options = nil)
-
@marked_for_destruction = false
-
super
-
end
-
-
# Marks this record to be destroyed as part of the parents save transaction.
-
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
-
# when <tt>parent.save</tt> is called.
-
#
-
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
-
1
def mark_for_destruction
-
@marked_for_destruction = true
-
end
-
-
# Returns whether or not this record will be destroyed as part of the parents save transaction.
-
#
-
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
-
1
def marked_for_destruction?
-
@marked_for_destruction
-
end
-
-
# Returns whether or not this record has been changed in any way (including whether
-
# any of its nested autosave associations are likewise changed)
-
1
def changed_for_autosave?
-
new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
-
end
-
-
1
private
-
-
# Returns the record for an association collection that should be validated
-
# or saved. If +autosave+ is +false+ only new records will be returned,
-
# unless the parent is/was a new record itself.
-
1
def associated_records_to_validate_or_save(association, new_record, autosave)
-
if new_record
-
association && association.target
-
elsif autosave
-
association.target.find_all { |record| record.changed_for_autosave? }
-
else
-
association.target.find_all { |record| record.new_record? }
-
end
-
end
-
-
# go through nested autosave associations that are loaded in memory (without loading
-
# any new ones), and return true if is changed for autosave
-
1
def nested_records_changed_for_autosave?
-
self.class.reflect_on_all_autosave_associations.any? do |reflection|
-
association = association_instance_get(reflection.name)
-
association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
-
end
-
end
-
-
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
-
# turned on for the association.
-
1
def validate_single_association(reflection)
-
association = association_instance_get(reflection.name)
-
record = association && association.reader
-
association_valid?(reflection, record) if record
-
end
-
-
# Validate the associated records if <tt>:validate</tt> or
-
# <tt>:autosave</tt> is turned on for the association specified by
-
# +reflection+.
-
1
def validate_collection_association(reflection)
-
if association = association_instance_get(reflection.name)
-
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
-
records.each { |record| association_valid?(reflection, record) }
-
end
-
end
-
end
-
-
# Returns whether or not the association is valid and applies any errors to
-
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
-
# enabled records if they're marked_for_destruction? or destroyed.
-
1
def association_valid?(reflection, record)
-
return true if record.destroyed? || record.marked_for_destruction?
-
-
unless valid = record.valid?(validation_context)
-
if reflection.options[:autosave]
-
record.errors.each do |attribute, message|
-
attribute = "#{reflection.name}.#{attribute}"
-
errors[attribute] << message
-
errors[attribute].uniq!
-
end
-
else
-
errors.add(reflection.name)
-
end
-
end
-
valid
-
end
-
-
# Is used as a before_save callback to check while saving a collection
-
# association whether or not the parent was a new record before saving.
-
1
def before_save_collection_association
-
@new_record_before_save = new_record?
-
true
-
end
-
-
# Saves any new associated records, or all loaded autosave associations if
-
# <tt>:autosave</tt> is enabled on the association.
-
#
-
# In addition, it destroys all children that were marked for destruction
-
# with mark_for_destruction.
-
#
-
# This all happens inside a transaction, _if_ the Transactions module is included into
-
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
-
1
def save_collection_association(reflection)
-
if association = association_instance_get(reflection.name)
-
autosave = reflection.options[:autosave]
-
-
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
-
records_to_destroy = []
-
records.each do |record|
-
next if record.destroyed?
-
-
saved = true
-
-
if autosave && record.marked_for_destruction?
-
records_to_destroy << record
-
elsif autosave != false && (@new_record_before_save || record.new_record?)
-
if autosave
-
saved = association.insert_record(record, false)
-
else
-
association.insert_record(record) unless reflection.nested?
-
end
-
elsif autosave
-
saved = record.save(:validate => false)
-
end
-
-
raise ActiveRecord::Rollback unless saved
-
end
-
-
records_to_destroy.each do |record|
-
association.destroy(record)
-
end
-
end
-
-
# reconstruct the scope now that we know the owner's id
-
association.send(:reset_scope) if association.respond_to?(:reset_scope)
-
end
-
end
-
-
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
-
# on the association.
-
#
-
# In addition, it will destroy the association if it was marked for
-
# destruction with mark_for_destruction.
-
#
-
# This all happens inside a transaction, _if_ the Transactions module is included into
-
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
-
1
def save_has_one_association(reflection)
-
association = association_instance_get(reflection.name)
-
record = association && association.load_target
-
if record && !record.destroyed?
-
autosave = reflection.options[:autosave]
-
-
if autosave && record.marked_for_destruction?
-
record.destroy
-
else
-
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
-
if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
-
unless reflection.through_reflection
-
record[reflection.foreign_key] = key
-
end
-
-
saved = record.save(:validate => !autosave)
-
raise ActiveRecord::Rollback if !saved && autosave
-
saved
-
end
-
end
-
end
-
end
-
-
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
-
#
-
# In addition, it will destroy the association if it was marked for destruction.
-
1
def save_belongs_to_association(reflection)
-
association = association_instance_get(reflection.name)
-
record = association && association.load_target
-
if record && !record.destroyed?
-
autosave = reflection.options[:autosave]
-
-
if autosave && record.marked_for_destruction?
-
self[reflection.foreign_key] = nil
-
record.destroy
-
elsif autosave != false
-
saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
-
-
if association.updated?
-
association_id = record.send(reflection.options[:primary_key] || :id)
-
self[reflection.foreign_key] = association_id
-
association.loaded!
-
end
-
-
saved if autosave
-
end
-
end
-
end
-
end
-
end
-
1
require 'yaml'
-
1
require 'set'
-
1
require 'active_support/benchmarkable'
-
1
require 'active_support/dependencies'
-
1
require 'active_support/descendants_tracker'
-
1
require 'active_support/time'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/class/delegating_attributes'
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/hash/deep_merge'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/string/behavior'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/module/introspection'
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'arel'
-
1
require 'active_record/errors'
-
1
require 'active_record/log_subscriber'
-
1
require 'active_record/explain_subscriber'
-
-
1
module ActiveRecord #:nodoc:
-
# = Active Record
-
#
-
# Active Record objects don't specify their attributes directly, but rather infer them from
-
# the table definition with which they're linked. Adding, removing, and changing attributes
-
# and their type is done directly in the database. Any change is instantly reflected in the
-
# Active Record objects. The mapping that binds a given Active Record class to a certain
-
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
-
#
-
# See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight.
-
#
-
# == Creation
-
#
-
# Active Records accept constructor parameters either in a hash or as a block. The hash
-
# method is especially useful when you're receiving the data from somewhere else, like an
-
# HTTP request. It works like this:
-
#
-
# user = User.new(:name => "David", :occupation => "Code Artist")
-
# user.name # => "David"
-
#
-
# You can also use block initialization:
-
#
-
# user = User.new do |u|
-
# u.name = "David"
-
# u.occupation = "Code Artist"
-
# end
-
#
-
# And of course you can just create a bare object and specify the attributes after the fact:
-
#
-
# user = User.new
-
# user.name = "David"
-
# user.occupation = "Code Artist"
-
#
-
# == Conditions
-
#
-
# Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
-
# The array form is to be used when the condition input is tainted and requires sanitization. The string form can
-
# be used for statements that don't involve tainted data. The hash form works much like the array form, except
-
# only equality and range is possible. Examples:
-
#
-
# class User < ActiveRecord::Base
-
# def self.authenticate_unsafely(user_name, password)
-
# where("user_name = '#{user_name}' AND password = '#{password}'").first
-
# end
-
#
-
# def self.authenticate_safely(user_name, password)
-
# where("user_name = ? AND password = ?", user_name, password).first
-
# end
-
#
-
# def self.authenticate_safely_simply(user_name, password)
-
# where(:user_name => user_name, :password => password).first
-
# end
-
# end
-
#
-
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
-
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
-
# parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
-
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
-
# before inserting them in the query, which will ensure that an attacker can't escape the
-
# query and fake the login (or worse).
-
#
-
# When using multiple parameters in the conditions, it can easily become hard to read exactly
-
# what the fourth or fifth question mark is supposed to represent. In those cases, you can
-
# resort to named bind variables instead. That's done by replacing the question marks with
-
# symbols and supplying a hash with values for the matching symbol keys:
-
#
-
# Company.where(
-
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
-
# { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
-
# ).first
-
#
-
# Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
-
# operator. For instance:
-
#
-
# Student.where(:first_name => "Harvey", :status => 1)
-
# Student.where(params[:student])
-
#
-
# A range may be used in the hash to use the SQL BETWEEN operator:
-
#
-
# Student.where(:grade => 9..12)
-
#
-
# An array may be used in the hash to use the SQL IN operator:
-
#
-
# Student.where(:grade => [9,11,12])
-
#
-
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
-
# can be used to qualify the table name of a particular condition. For instance:
-
#
-
# Student.joins(:schools).where(:schools => { :category => 'public' })
-
# Student.joins(:schools).where('schools.category' => 'public' )
-
#
-
# == Overwriting default accessors
-
#
-
# All column values are automatically available through basic accessors on the Active Record
-
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
-
# the default accessors (using the same name as the attribute) and calling
-
# <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
-
# change things.
-
#
-
# class Song < ActiveRecord::Base
-
# # Uses an integer of seconds to hold the length of the song
-
#
-
# def length=(minutes)
-
# write_attribute(:length, minutes.to_i * 60)
-
# end
-
#
-
# def length
-
# read_attribute(:length) / 60
-
# end
-
# end
-
#
-
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
-
# instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
-
#
-
# == Attribute query methods
-
#
-
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
-
# Query methods allow you to test whether an attribute value is present.
-
#
-
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
-
# to determine whether the user has a name:
-
#
-
# user = User.new(:name => "David")
-
# user.name? # => true
-
#
-
# anonymous = User.new(:name => "")
-
# anonymous.name? # => false
-
#
-
# == Accessing attributes before they have been typecasted
-
#
-
# Sometimes you want to be able to read the raw attribute data without having the column-determined
-
# typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
-
# accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
-
# you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
-
#
-
# This is especially useful in validation situations where the user might supply a string for an
-
# integer field and you want to display the original string back in an error message. Accessing the
-
# attribute normally would typecast the string to 0, which isn't what you want.
-
#
-
# == Dynamic attribute-based finders
-
#
-
# Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
-
# by simple queries without turning to SQL. They work by appending the name of an attribute
-
# to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders
-
# like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and
-
# <tt>Payment.find_by_transaction_id</tt>. Instead of writing
-
# <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
-
# And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
-
# <tt>Person.find_all_by_last_name(last_name)</tt>.
-
#
-
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
-
# <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
-
# like <tt>Person.find_by_last_name!</tt>.
-
#
-
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
-
#
-
# Person.where(:user_name => user_name, :password => password).first
-
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
-
#
-
# It's even possible to call these dynamic finder methods on relations and named scopes.
-
#
-
# Payment.order("created_on").find_all_by_amount(50)
-
# Payment.pending.find_last_by_amount(100)
-
#
-
# The same dynamic finder style can be used to create the object if it doesn't already exist.
-
# This dynamic finder is called with <tt>find_or_create_by_</tt> and will return the object if
-
# it already exists and otherwise creates it, then returns it. Protected attributes won't be set
-
# unless they are given in a block.
-
#
-
# # No 'Summer' tag exists
-
# Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
-
#
-
# # Now the 'Summer' tag does exist
-
# Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
-
#
-
# # Now 'Bob' exist and is an 'admin'
-
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
-
#
-
# Adding an exclamation point (!) on to the end of <tt>find_or_create_by_</tt> will
-
# raise an <tt>ActiveRecord::RecordInvalid</tt> error if the new record is invalid.
-
#
-
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
-
# saving it first. Protected attributes won't be set unless they are given in a block.
-
#
-
# # No 'Winter' tag exists
-
# winter = Tag.find_or_initialize_by_name("Winter")
-
# winter.persisted? # false
-
#
-
# To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
-
# a list of parameters.
-
#
-
# Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
-
#
-
# That will either find an existing tag named "rails", or create a new one while setting the
-
# user that created it.
-
#
-
# Just like <tt>find_by_*</tt>, you can also use <tt>scoped_by_*</tt> to retrieve data. The good thing about
-
# using this feature is that the very first time result is returned using <tt>method_missing</tt> technique
-
# but after that the method is declared on the class. Henceforth <tt>method_missing</tt> will not be hit.
-
#
-
# User.scoped_by_user_name('David')
-
#
-
# == Saving arrays, hashes, and other non-mappable objects in text columns
-
#
-
# Active Record can serialize any object in text columns using YAML. To do so, you must
-
# specify this with a call to the class method +serialize+.
-
# This makes it possible to store arrays, hashes, and other non-mappable objects without doing
-
# any additional work.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences
-
# end
-
#
-
# user = User.create(:preferences => { "background" => "black", "display" => large })
-
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
-
#
-
# You can also specify a class option as the second parameter that'll raise an exception
-
# if a serialized object is retrieved as a descendant of a class not in the hierarchy.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences, Hash
-
# end
-
#
-
# user = User.create(:preferences => %w( one two three ))
-
# User.find(user.id).preferences # raises SerializationTypeMismatch
-
#
-
# When you specify a class option, the default value for that attribute will be a new
-
# instance of that class.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences, OpenStruct
-
# end
-
#
-
# user = User.new
-
# user.preferences.theme_color = "red"
-
#
-
#
-
# == Single table inheritance
-
#
-
# Active Record allows inheritance by storing the name of the class in a column that by
-
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
-
# This means that an inheritance looking like this:
-
#
-
# class Company < ActiveRecord::Base; end
-
# class Firm < Company; end
-
# class Client < Company; end
-
# class PriorityClient < Client; end
-
#
-
# When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in
-
# the companies table with type = "Firm". You can then fetch this row again using
-
# <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object.
-
#
-
# If you don't have a type column defined in your table, single-table inheritance won't
-
# be triggered. In that case, it'll work just like normal subclasses with no special magic
-
# for differentiating between them or reloading the right type with find.
-
#
-
# Note, all the attributes for all the cases are kept in the same table. Read more:
-
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
-
#
-
# == Connection to multiple databases in different models
-
#
-
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
-
# by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
-
# connection. But you can also set a class-specific connection. For example, if Course is an
-
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
-
# and Course and all of its subclasses will use this connection instead.
-
#
-
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
-
# a Hash indexed by the class. If a connection is requested, the retrieve_connection method
-
# will go up the class-hierarchy until a connection is found in the connection pool.
-
#
-
# == Exceptions
-
#
-
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
-
# * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
-
# <tt>:adapter</tt> key.
-
# * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
-
# non-existent adapter
-
# (or a bad spelling of an existing one).
-
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
-
# specified in the association definition.
-
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
-
# <tt>attributes=</tt> method.
-
# You can inspect the +attribute+ property of the exception object to determine which attribute
-
# triggered the error.
-
# * ConnectionNotEstablished - No connection has been established. Use <tt>establish_connection</tt>
-
# before querying.
-
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
-
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
-
# AttributeAssignmentError
-
# objects that should be inspected to determine which attributes triggered the errors.
-
# * RecordInvalid - raised by save! and create! when the record is invalid.
-
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
-
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
-
# nothing was found, please check its documentation for further details.
-
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
-
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
-
#
-
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
-
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
-
# instances in the current object space.
-
1
class Base
-
1
extend ActiveModel::Observing::ClassMethods
-
1
extend ActiveModel::Naming
-
-
1
extend ActiveSupport::Benchmarkable
-
1
extend ActiveSupport::DescendantsTracker
-
-
1
extend ConnectionHandling
-
1
extend QueryCache::ClassMethods
-
1
extend Querying
-
1
extend Translation
-
1
extend DynamicMatchers
-
1
extend Explain
-
-
1
include Persistence
-
1
include ReadonlyAttributes
-
1
include ModelSchema
-
1
include Inheritance
-
1
include Scoping
-
1
include Sanitization
-
1
include AttributeAssignment
-
1
include ActiveModel::Conversion
-
1
include Integration
-
1
include Validations
-
1
include CounterCache
-
1
include Locking::Optimistic
-
1
include Locking::Pessimistic
-
1
include AttributeMethods
-
1
include Callbacks
-
1
include ActiveModel::Observing
-
1
include Timestamp
-
1
include Associations
-
1
include ActiveModel::SecurePassword
-
1
include AutosaveAssociation
-
1
include NestedAttributes
-
1
include Aggregations
-
1
include Transactions
-
1
include Reflection
-
1
include Serialization
-
1
include Store
-
1
include Core
-
end
-
-
1
ActiveSupport.run_load_hooks(:active_record, Base)
-
end
-
1
module ActiveRecord
-
# = Active Record Callbacks
-
#
-
# Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
-
# before or after an alteration of the object state. This can be used to make sure that associated and
-
# dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
-
# before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
-
# the <tt>Base#save</tt> call for a new record:
-
#
-
# * (-) <tt>save</tt>
-
# * (-) <tt>valid</tt>
-
# * (1) <tt>before_validation</tt>
-
# * (-) <tt>validate</tt>
-
# * (2) <tt>after_validation</tt>
-
# * (3) <tt>before_save</tt>
-
# * (4) <tt>before_create</tt>
-
# * (-) <tt>create</tt>
-
# * (5) <tt>after_create</tt>
-
# * (6) <tt>after_save</tt>
-
# * (7) <tt>after_commit</tt>
-
#
-
# Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
-
# Check out <tt>ActiveRecord::Transactions</tt> for more details about <tt>after_commit</tt> and
-
# <tt>after_rollback</tt>.
-
#
-
# Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
-
# is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
-
# are instantiated as well.
-
#
-
# That's a total of twelve callbacks, which gives you immense power to react and prepare for each state in the
-
# Active Record life cycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
-
# except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
-
#
-
# Examples:
-
# class CreditCard < ActiveRecord::Base
-
# # Strip everything but digits, so the user can specify "555 234 34" or
-
# # "5552-3434" and both will mean "55523434"
-
# before_validation(:on => :create) do
-
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
-
# end
-
# end
-
#
-
# class Subscription < ActiveRecord::Base
-
# before_create :record_signup
-
#
-
# private
-
# def record_signup
-
# self.signed_up_on = Date.today
-
# end
-
# end
-
#
-
# class Firm < ActiveRecord::Base
-
# # Destroys the associated clients and people when the firm is destroyed
-
# before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
-
# before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
-
# end
-
#
-
# == Inheritable callback queues
-
#
-
# Besides the overwritable callback methods, it's also possible to register callbacks through the
-
# use of the callback macros. Their main advantage is that the macros add behavior into a callback
-
# queue that is kept intact down through an inheritance hierarchy.
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy :destroy_author
-
# end
-
#
-
# class Reply < Topic
-
# before_destroy :destroy_readers
-
# end
-
#
-
# Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
-
# run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation
-
# where the +before_destroy+ method is overridden:
-
#
-
# class Topic < ActiveRecord::Base
-
# def before_destroy() destroy_author end
-
# end
-
#
-
# class Reply < Topic
-
# def before_destroy() destroy_readers end
-
# end
-
#
-
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
-
# So, use the callback macros when you want to ensure that a certain callback is called for the entire
-
# hierarchy, and use the regular overwriteable methods when you want to leave it up to each descendant
-
# to decide whether they want to call +super+ and trigger the inherited callbacks.
-
#
-
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
-
# callbacks before specifying the associations. Otherwise, you might trigger the loading of a
-
# child before the parent has registered the callbacks and they won't be inherited.
-
#
-
# == Types of callbacks
-
#
-
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
-
# inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects
-
# are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for
-
# creating mix-ins), and inline eval methods are deprecated.
-
#
-
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy :delete_parents
-
#
-
# private
-
# def delete_parents
-
# self.class.delete_all "parent_id = #{id}"
-
# end
-
# end
-
#
-
# The callback objects have methods named after the callback called with the record as the only parameter, such as:
-
#
-
# class BankAccount < ActiveRecord::Base
-
# before_save EncryptionWrapper.new
-
# after_save EncryptionWrapper.new
-
# after_initialize EncryptionWrapper.new
-
# end
-
#
-
# class EncryptionWrapper
-
# def before_save(record)
-
# record.credit_card_number = encrypt(record.credit_card_number)
-
# end
-
#
-
# def after_save(record)
-
# record.credit_card_number = decrypt(record.credit_card_number)
-
# end
-
#
-
# alias_method :after_find, :after_save
-
#
-
# private
-
# def encrypt(value)
-
# # Secrecy is committed
-
# end
-
#
-
# def decrypt(value)
-
# # Secrecy is unveiled
-
# end
-
# end
-
#
-
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
-
# a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
-
# initialization data such as the name of the attribute to work with:
-
#
-
# class BankAccount < ActiveRecord::Base
-
# before_save EncryptionWrapper.new("credit_card_number")
-
# after_save EncryptionWrapper.new("credit_card_number")
-
# after_initialize EncryptionWrapper.new("credit_card_number")
-
# end
-
#
-
# class EncryptionWrapper
-
# def initialize(attribute)
-
# @attribute = attribute
-
# end
-
#
-
# def before_save(record)
-
# record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
-
# end
-
#
-
# def after_save(record)
-
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
-
# end
-
#
-
# alias_method :after_find, :after_save
-
#
-
# private
-
# def encrypt(value)
-
# # Secrecy is committed
-
# end
-
#
-
# def decrypt(value)
-
# # Secrecy is unveiled
-
# end
-
# end
-
#
-
# The callback macros usually accept a symbol for the method they're supposed to run, but you can also
-
# pass a "method string", which will then be evaluated within the binding of the callback. Example:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
-
# end
-
#
-
# Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
-
# is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
-
# 'puts "Evaluated after parents are destroyed"'
-
# end
-
#
-
# == <tt>before_validation*</tt> returning statements
-
#
-
# If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
-
# aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
-
# ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
-
#
-
# == Canceling callbacks
-
#
-
# If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
-
# cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
-
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
-
# methods on the model, which are called last.
-
#
-
# == Ordering callbacks
-
#
-
# Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
-
# callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option.
-
#
-
# Let's look at the code below:
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children, dependent: destroy
-
#
-
# before_destroy :log_children
-
#
-
# private
-
# def log_children
-
# # Child processing
-
# end
-
# end
-
#
-
# In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available
-
# because the +destroy+ callback gets executed first. You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children, dependent: destroy
-
#
-
# before_destroy :log_children, prepend: true
-
#
-
# private
-
# def log_children
-
# # Child processing
-
# end
-
# end
-
#
-
# This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available.
-
#
-
# == Transactions
-
#
-
# The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
-
# within a transaction. That includes <tt>after_*</tt> hooks. If everything
-
# goes fine a COMMIT is executed once the chain has been completed.
-
#
-
# If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
-
# can also trigger a ROLLBACK raising an exception in any of the callbacks,
-
# including <tt>after_*</tt> hooks. Note, however, that in that case the client
-
# needs to be aware of it because an ordinary +save+ will raise such exception
-
# instead of quietly returning +false+.
-
#
-
# == Debugging callbacks
-
#
-
# The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
-
# <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
-
# defines what part of the chain the callback runs in.
-
#
-
# To find all callbacks in the before_save callback chain:
-
#
-
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
-
#
-
# Returns an array of callback objects that form the before_save chain.
-
#
-
# To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
-
#
-
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
-
#
-
# Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
-
#
-
1
module Callbacks
-
1
extend ActiveSupport::Concern
-
-
1
CALLBACKS = [
-
:after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
-
:before_save, :around_save, :after_save, :before_create, :around_create,
-
:after_create, :before_update, :around_update, :after_update,
-
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
-
]
-
-
1
module ClassMethods
-
1
include ActiveModel::Callbacks
-
end
-
-
1
included do
-
1
include ActiveModel::Validations::Callbacks
-
-
1
define_model_callbacks :initialize, :find, :touch, :only => :after
-
1
define_model_callbacks :save, :create, :update, :destroy
-
end
-
-
1
def destroy #:nodoc:
-
run_callbacks(:destroy) { super }
-
end
-
-
1
def touch(*) #:nodoc:
-
run_callbacks(:touch) { super }
-
end
-
-
1
private
-
-
1
def create_or_update #:nodoc:
-
run_callbacks(:save) { super }
-
end
-
-
1
def create #:nodoc:
-
run_callbacks(:create) { super }
-
end
-
-
1
def update(*) #:nodoc:
-
run_callbacks(:update) { super }
-
end
-
end
-
end
-
1
require 'thread'
-
1
require 'monitor'
-
1
require 'set'
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord
-
# Raised when a connection could not be obtained within the connection
-
# acquisition timeout period: because max connections in pool
-
# are in use.
-
1
class ConnectionTimeoutError < ConnectionNotEstablished
-
end
-
-
1
module ConnectionAdapters
-
# Connection pool base class for managing Active Record database
-
# connections.
-
#
-
# == Introduction
-
#
-
# A connection pool synchronizes thread access to a limited number of
-
# database connections. The basic idea is that each thread checks out a
-
# database connection from the pool, uses that connection, and checks the
-
# connection back in. ConnectionPool is completely thread-safe, and will
-
# ensure that a connection cannot be used by two threads at the same time,
-
# as long as ConnectionPool's contract is correctly followed. It will also
-
# handle cases in which there are more threads than connections: if all
-
# connections have been checked out, and a thread tries to checkout a
-
# connection anyway, then ConnectionPool will wait until some other thread
-
# has checked in a connection.
-
#
-
# == Obtaining (checking out) a connection
-
#
-
# Connections can be obtained and used from a connection pool in several
-
# ways:
-
#
-
# 1. Simply use ActiveRecord::Base.connection as with Active Record 2.1 and
-
# earlier (pre-connection-pooling). Eventually, when you're done with
-
# the connection(s) and wish it to be returned to the pool, you call
-
# ActiveRecord::Base.clear_active_connections!. This will be the
-
# default behavior for Active Record when used in conjunction with
-
# Action Pack's request handling cycle.
-
# 2. Manually check out a connection from the pool with
-
# ActiveRecord::Base.connection_pool.checkout. You are responsible for
-
# returning this connection to the pool when finished by calling
-
# ActiveRecord::Base.connection_pool.checkin(connection).
-
# 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
-
# obtains a connection, yields it as the sole argument to the block,
-
# and returns it to the pool after the block completes.
-
#
-
# Connections in the pool are actually AbstractAdapter objects (or objects
-
# compatible with AbstractAdapter's interface).
-
#
-
# == Options
-
#
-
# There are several connection-pooling-related options that you can add to
-
# your database connection configuration:
-
#
-
# * +pool+: number indicating size of connection pool (default 5)
-
# * +checkout_timeout+: number of seconds to block and wait for a connection
-
# before giving up and raising a timeout error (default 5 seconds).
-
# * +reaping_frequency+: frequency in seconds to periodically run the
-
# Reaper, which attempts to find and close dead connections, which can
-
# occur if a programmer forgets to close a connection at the end of a
-
# thread or a thread dies unexpectedly. (Default nil, which means don't
-
# run the Reaper).
-
# * +dead_connection_timeout+: number of seconds from last checkout
-
# after which the Reaper will consider a connection reapable. (default
-
# 5 seconds).
-
1
class ConnectionPool
-
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
-
# with which it shares a Monitor. But could be a generic Queue.
-
#
-
# The Queue in stdlib's 'thread' could replace this class except
-
# stdlib's doesn't support waiting with a timeout.
-
1
class Queue
-
1
def initialize(lock = Monitor.new)
-
1
@lock = lock
-
1
@cond = @lock.new_cond
-
1
@num_waiting = 0
-
1
@queue = []
-
end
-
-
# Test if any threads are currently waiting on the queue.
-
1
def any_waiting?
-
synchronize do
-
@num_waiting > 0
-
end
-
end
-
-
# Return the number of threads currently waiting on this
-
# queue.
-
1
def num_waiting
-
synchronize do
-
@num_waiting
-
end
-
end
-
-
# Add +element+ to the queue. Never blocks.
-
1
def add(element)
-
synchronize do
-
@queue.push element
-
@cond.signal
-
end
-
end
-
-
# If +element+ is in the queue, remove and return it, or nil.
-
1
def delete(element)
-
synchronize do
-
@queue.delete(element)
-
end
-
end
-
-
# Remove all elements from the queue.
-
1
def clear
-
synchronize do
-
@queue.clear
-
end
-
end
-
-
# Remove the head of the queue.
-
#
-
# If +timeout+ is not given, remove and return the head the
-
# queue if the number of available elements is strictly
-
# greater than the number of threads currently waiting (that
-
# is, don't jump ahead in line). Otherwise, return nil.
-
#
-
# If +timeout+ is given, block if it there is no element
-
# available, waiting up to +timeout+ seconds for an element to
-
# become available.
-
#
-
# Raises:
-
# - ConnectionTimeoutError if +timeout+ is given and no element
-
# becomes available after +timeout+ seconds,
-
1
def poll(timeout = nil)
-
1
synchronize do
-
1
if timeout
-
no_wait_poll || wait_poll(timeout)
-
else
-
1
no_wait_poll
-
end
-
end
-
end
-
-
1
private
-
-
1
def synchronize(&block)
-
1
@lock.synchronize(&block)
-
end
-
-
# Test if the queue currently contains any elements.
-
1
def any?
-
!@queue.empty?
-
end
-
-
# A thread can remove an element from the queue without
-
# waiting if an only if the number of currently available
-
# connections is strictly greater than the number of waiting
-
# threads.
-
1
def can_remove_no_wait?
-
1
@queue.size > @num_waiting
-
end
-
-
# Removes and returns the head of the queue if possible, or nil.
-
1
def remove
-
@queue.shift
-
end
-
-
# Remove and return the head the queue if the number of
-
# available elements is strictly greater than the number of
-
# threads currently waiting. Otherwise, return nil.
-
1
def no_wait_poll
-
1
remove if can_remove_no_wait?
-
end
-
-
# Waits on the queue up to +timeout+ seconds, then removes and
-
# returns the head of the queue.
-
1
def wait_poll(timeout)
-
@num_waiting += 1
-
-
t0 = Time.now
-
elapsed = 0
-
loop do
-
@cond.wait(timeout - elapsed)
-
-
return remove if any?
-
-
elapsed = Time.now - t0
-
if elapsed >= timeout
-
msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
-
[timeout, elapsed]
-
raise ConnectionTimeoutError, msg
-
end
-
end
-
ensure
-
@num_waiting -= 1
-
end
-
end
-
-
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
-
# A reaper instantiated with a nil frequency will never reap the
-
# connection pool.
-
#
-
# Configure the frequency by setting "reaping_frequency" in your
-
# database yaml file.
-
1
class Reaper
-
1
attr_reader :pool, :frequency
-
-
1
def initialize(pool, frequency)
-
1
@pool = pool
-
1
@frequency = frequency
-
end
-
-
1
def run
-
1
return unless frequency
-
Thread.new(frequency, pool) { |t, p|
-
while true
-
sleep t
-
p.reap
-
end
-
}
-
end
-
end
-
-
1
include MonitorMixin
-
-
1
attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout
-
1
attr_reader :spec, :connections, :size, :reaper
-
-
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
-
# object which describes database connection information (e.g. adapter,
-
# host name, username, password, etc), as well as the maximum size for
-
# this ConnectionPool.
-
#
-
# The default ConnectionPool maximum size is 5.
-
1
def initialize(spec)
-
1
super()
-
-
1
@spec = spec
-
-
# The cache of reserved connections mapped to threads
-
1
@reserved_connections = {}
-
-
1
@checkout_timeout = spec.config[:checkout_timeout] || 5
-
1
@dead_connection_timeout = spec.config[:dead_connection_timeout]
-
1
@reaper = Reaper.new self, spec.config[:reaping_frequency]
-
1
@reaper.run
-
-
# default max pool size to 5
-
1
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
-
-
1
@connections = []
-
1
@automatic_reconnect = true
-
-
1
@available = Queue.new self
-
end
-
-
# Hack for tests to be able to add connections. Do not call outside of tests
-
1
def insert_connection_for_test!(c) #:nodoc:
-
synchronize do
-
@connections << c
-
@available.add c
-
end
-
end
-
-
# Retrieve the connection associated with the current thread, or call
-
# #checkout to obtain one if necessary.
-
#
-
# #connection can be called any number of times; the connection is
-
# held in a hash keyed by the thread id.
-
1
def connection
-
9
synchronize do
-
9
@reserved_connections[current_connection_id] ||= checkout
-
end
-
end
-
-
# Is there an open connection that is being used for the current thread?
-
1
def active_connection?
-
synchronize do
-
@reserved_connections.fetch(current_connection_id) {
-
return false
-
}.in_use?
-
end
-
end
-
-
# Signal that the thread is finished with the current connection.
-
# #release_connection releases the connection-thread association
-
# and returns the connection to the pool.
-
1
def release_connection(with_id = current_connection_id)
-
synchronize do
-
conn = @reserved_connections.delete(with_id)
-
checkin conn if conn
-
end
-
end
-
-
# If a connection already exists yield it to the block. If no connection
-
# exists checkout a connection, yield it to the block, and checkin the
-
# connection when finished.
-
1
def with_connection
-
connection_id = current_connection_id
-
fresh_connection = true unless active_connection?
-
yield connection
-
ensure
-
release_connection(connection_id) if fresh_connection
-
end
-
-
# Returns true if a connection has already been opened.
-
1
def connected?
-
synchronize { @connections.any? }
-
end
-
-
# Disconnects all connections in the pool, and clears the pool.
-
1
def disconnect!
-
synchronize do
-
@reserved_connections = {}
-
@connections.each do |conn|
-
checkin conn
-
conn.disconnect!
-
end
-
@connections = []
-
@available.clear
-
end
-
end
-
-
# Clears the cache which maps classes.
-
1
def clear_reloadable_connections!
-
synchronize do
-
@reserved_connections = {}
-
@connections.each do |conn|
-
checkin conn
-
conn.disconnect! if conn.requires_reloading?
-
end
-
@connections.delete_if do |conn|
-
conn.requires_reloading?
-
end
-
@available.clear
-
@connections.each do |conn|
-
@available.add conn
-
end
-
end
-
end
-
-
1
def clear_stale_cached_connections! # :nodoc:
-
reap
-
end
-
1
deprecate :clear_stale_cached_connections! => "Please use #reap instead"
-
-
# Check-out a database connection from the pool, indicating that you want
-
# to use it. You should call #checkin when you no longer need this.
-
#
-
# This is done by either returning and leasing existing connection, or by
-
# creating a new connection and leasing it.
-
#
-
# If all connections are leased and the pool is at capacity (meaning the
-
# number of currently leased connections is greater than or equal to the
-
# size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
-
#
-
# Returns: an AbstractAdapter object.
-
#
-
# Raises:
-
# - ConnectionTimeoutError: no connection can be obtained from the pool.
-
1
def checkout
-
1
synchronize do
-
1
conn = acquire_connection
-
1
conn.lease
-
1
checkout_and_verify(conn)
-
end
-
end
-
-
# Check-in a database connection back into the pool, indicating that you
-
# no longer need this connection.
-
#
-
# +conn+: an AbstractAdapter object, which was obtained by earlier by
-
# calling +checkout+ on this pool.
-
1
def checkin(conn)
-
synchronize do
-
conn.run_callbacks :checkin do
-
conn.expire
-
end
-
-
release conn
-
-
@available.add conn
-
end
-
end
-
-
# Remove a connection from the connection pool. The connection will
-
# remain open and active but will no longer be managed by this pool.
-
1
def remove(conn)
-
synchronize do
-
@connections.delete conn
-
@available.delete conn
-
-
# FIXME: we might want to store the key on the connection so that removing
-
# from the reserved hash will be a little easier.
-
release conn
-
-
@available.add checkout_new_connection if @available.any_waiting?
-
end
-
end
-
-
# Removes dead connections from the pool. A dead connection can occur
-
# if a programmer forgets to close a connection at the end of a thread
-
# or a thread dies unexpectedly.
-
1
def reap
-
synchronize do
-
stale = Time.now - @dead_connection_timeout
-
connections.dup.each do |conn|
-
remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
-
end
-
end
-
end
-
-
1
private
-
-
# Acquire a connection by one of 1) immediately removing one
-
# from the queue of available connections, 2) creating a new
-
# connection if the pool is not at capacity, 3) waiting on the
-
# queue for a connection to become available.
-
#
-
# Raises:
-
# - ConnectionTimeoutError if a connection could not be acquired
-
1
def acquire_connection
-
1
if conn = @available.poll
-
conn
-
1
elsif @connections.size < @size
-
1
checkout_new_connection
-
else
-
@available.poll(@checkout_timeout)
-
end
-
end
-
-
1
def release(conn)
-
thread_id = if @reserved_connections[current_connection_id] == conn
-
current_connection_id
-
else
-
@reserved_connections.keys.find { |k|
-
@reserved_connections[k] == conn
-
}
-
end
-
-
@reserved_connections.delete thread_id if thread_id
-
end
-
-
1
def new_connection
-
1
Base.send(spec.adapter_method, spec.config)
-
end
-
-
1
def current_connection_id #:nodoc:
-
9
Base.connection_id ||= Thread.current.object_id
-
end
-
-
1
def checkout_new_connection
-
1
raise ConnectionNotEstablished unless @automatic_reconnect
-
-
1
c = new_connection
-
1
c.pool = self
-
1
@connections << c
-
1
c
-
end
-
-
1
def checkout_and_verify(c)
-
1
c.run_callbacks :checkout do
-
1
c.verify!
-
end
-
1
c
-
end
-
end
-
-
# ConnectionHandler is a collection of ConnectionPool objects. It is used
-
# for keeping separate connection pools for Active Record models that connect
-
# to different databases.
-
#
-
# For example, suppose that you have 5 models, with the following hierarchy:
-
#
-
# |
-
# +-- Book
-
# | |
-
# | +-- ScaryBook
-
# | +-- GoodBook
-
# +-- Author
-
# +-- BankAccount
-
#
-
# Suppose that Book is to connect to a separate database (i.e. one other
-
# than the default database). Then Book, ScaryBook and GoodBook will all use
-
# the same connection pool. Likewise, Author and BankAccount will use the
-
# same connection pool. However, the connection pool used by Author/BankAccount
-
# is not the same as the one used by Book/ScaryBook/GoodBook.
-
#
-
# Normally there is only a single ConnectionHandler instance, accessible via
-
# ActiveRecord::Base.connection_handler. Active Record models use this to
-
# determine the connection pool that they should use.
-
1
class ConnectionHandler
-
1
def initialize
-
2
@owner_to_pool = Hash.new { |h,k| h[k] = {} }
-
2
@class_to_pool = Hash.new { |h,k| h[k] = {} }
-
end
-
-
1
def connection_pool_list
-
owner_to_pool.values.compact
-
end
-
-
1
def connection_pools
-
ActiveSupport::Deprecation.warn(
-
"In the next release, this will return the same as #connection_pool_list. " \
-
"(An array of pools, rather than a hash mapping specs to pools.)"
-
)
-
Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
-
end
-
-
1
def establish_connection(owner, spec)
-
1
@class_to_pool.clear
-
1
owner_to_pool[owner] = ConnectionAdapters::ConnectionPool.new(spec)
-
end
-
-
# Returns true if there are any active connections among the connection
-
# pools that the ConnectionHandler is managing.
-
1
def active_connections?
-
connection_pool_list.any?(&:active_connection?)
-
end
-
-
# Returns any connections in use by the current thread back to the pool,
-
# and also returns connections to the pool cached by threads that are no
-
# longer alive.
-
1
def clear_active_connections!
-
connection_pool_list.each(&:release_connection)
-
end
-
-
# Clears the cache which maps classes.
-
1
def clear_reloadable_connections!
-
connection_pool_list.each(&:clear_reloadable_connections!)
-
end
-
-
1
def clear_all_connections!
-
connection_pool_list.each(&:disconnect!)
-
end
-
-
# Locate the connection of the nearest super class. This can be an
-
# active or defined connection: if it is the latter, it will be
-
# opened and set as the active connection for the class it was defined
-
# for (not necessarily the current class).
-
1
def retrieve_connection(klass) #:nodoc:
-
9
pool = retrieve_connection_pool(klass)
-
9
(pool && pool.connection) or raise ConnectionNotEstablished
-
end
-
-
# Returns true if a connection that's accessible to this class has
-
# already been opened.
-
1
def connected?(klass)
-
conn = retrieve_connection_pool(klass)
-
conn && conn.connected?
-
end
-
-
# Remove the connection for this class. This will close the active
-
# connection and the defined connection (if they exist). The result
-
# can be used as an argument for establish_connection, for easily
-
# re-establishing the connection.
-
1
def remove_connection(owner)
-
1
if pool = owner_to_pool.delete(owner)
-
@class_to_pool.clear
-
pool.automatic_reconnect = false
-
pool.disconnect!
-
pool.spec.config
-
end
-
end
-
-
# Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
-
# This makes retrieving the connection pool O(1) once the process is warm.
-
# When a connection is established or removed, we invalidate the cache.
-
#
-
# Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
-
# However, benchmarking (https://gist.github.com/3552829) showed that #fetch is
-
# significantly slower than #[]. So in the nil case, no caching will take place,
-
# but that's ok since the nil case is not the common one that we wish to optimise
-
# for.
-
1
def retrieve_connection_pool(klass)
-
10
class_to_pool[klass] ||= begin
-
2
until pool = pool_for(klass)
-
1
klass = klass.superclass
-
1
break unless klass <= Base
-
end
-
-
2
class_to_pool[klass] = pool
-
end
-
end
-
-
1
private
-
-
1
def owner_to_pool
-
6
@owner_to_pool[Process.pid]
-
end
-
-
1
def class_to_pool
-
12
@class_to_pool[Process.pid]
-
end
-
-
1
def pool_for(owner)
-
3
owner_to_pool.fetch(owner) {
-
1
if ancestor_pool = pool_from_any_process_for(owner)
-
# A connection was established in an ancestor process that must have
-
# subsequently forked. We can't reuse the connection, but we can copy
-
# the specification and establish a new connection with it.
-
establish_connection owner, ancestor_pool.spec
-
else
-
1
owner_to_pool[owner] = nil
-
end
-
}
-
end
-
-
1
def pool_from_any_process_for(owner)
-
2
owner_to_pool = @owner_to_pool.values.find { |v| v[owner] }
-
1
owner_to_pool && owner_to_pool[owner]
-
end
-
end
-
-
1
class ConnectionManagement
-
1
def initialize(app)
-
@app = app
-
end
-
-
1
def call(env)
-
testing = env.key?('rack.test')
-
-
response = @app.call(env)
-
response[2] = ::Rack::BodyProxy.new(response[2]) do
-
ActiveRecord::Base.clear_active_connections! unless testing
-
end
-
-
response
-
rescue
-
ActiveRecord::Base.clear_active_connections! unless testing
-
raise
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters # :nodoc:
-
1
module DatabaseLimits
-
-
# Returns the maximum length of a table alias.
-
1
def table_alias_length
-
255
-
end
-
-
# Returns the maximum length of a column name.
-
1
def column_name_length
-
64
-
end
-
-
# Returns the maximum length of a table name.
-
1
def table_name_length
-
64
-
end
-
-
# Returns the maximum length of an index name.
-
1
def index_name_length
-
64
-
end
-
-
# Returns the maximum number of columns per table.
-
1
def columns_per_table
-
1024
-
end
-
-
# Returns the maximum number of indexes per table.
-
1
def indexes_per_table
-
16
-
end
-
-
# Returns the maximum number of columns in a multicolumn index.
-
1
def columns_per_multicolumn_index
-
16
-
end
-
-
# Returns the maximum number of elements in an IN (x,y,z) clause.
-
# nil means no limit.
-
1
def in_clause_length
-
nil
-
end
-
-
# Returns the maximum length of an SQL query.
-
1
def sql_query_length
-
1048575
-
end
-
-
# Returns maximum number of joins in a single query.
-
1
def joins_per_query
-
256
-
end
-
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters # :nodoc:
-
1
module DatabaseStatements
-
1
def initialize
-
1
super
-
1
reset_transaction
-
end
-
-
# Converts an arel AST to SQL
-
1
def to_sql(arel, binds = [])
-
if arel.respond_to?(:ast)
-
binds = binds.dup
-
visitor.accept(arel.ast) do
-
quote(*binds.shift.reverse)
-
end
-
else
-
arel
-
end
-
end
-
-
# Returns an array of record hashes with the column names as keys and
-
# column values as values.
-
1
def select_all(arel, name = nil, binds = [])
-
select(to_sql(arel, binds), name, binds)
-
end
-
-
# Returns a record hash with the column names as keys and column values
-
# as values.
-
1
def select_one(arel, name = nil, binds = [])
-
result = select_all(arel, name, binds)
-
result.first if result
-
end
-
-
# Returns a single value from a record
-
1
def select_value(arel, name = nil, binds = [])
-
if result = select_one(arel, name, binds)
-
result.values.first
-
end
-
end
-
-
# Returns an array of the values of the first column in a select:
-
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
-
1
def select_values(arel, name = nil)
-
result = select_rows(to_sql(arel, []), name)
-
result.map { |v| v[0] }
-
end
-
-
# Returns an array of arrays containing the field values.
-
# Order is the same as that returned by +columns+.
-
1
def select_rows(sql, name = nil)
-
end
-
1
undef_method :select_rows
-
-
# Executes the SQL statement in the context of this connection.
-
1
def execute(sql, name = nil)
-
end
-
1
undef_method :execute
-
-
# Executes +sql+ statement in the context of this connection using
-
# +binds+ as the bind substitutes. +name+ is logged along with
-
# the executed +sql+ statement.
-
1
def exec_query(sql, name = 'SQL', binds = [])
-
end
-
-
# Executes insert +sql+ statement in the context of this connection using
-
# +binds+ as the bind substitutes. +name+ is logged along with
-
# the executed +sql+ statement.
-
1
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
-
exec_query(sql, name, binds)
-
end
-
-
# Executes delete +sql+ statement in the context of this connection using
-
# +binds+ as the bind substitutes. +name+ is logged along with
-
# the executed +sql+ statement.
-
1
def exec_delete(sql, name, binds)
-
exec_query(sql, name, binds)
-
end
-
-
# Executes update +sql+ statement in the context of this connection using
-
# +binds+ as the bind substitutes. +name+ is logged along with
-
# the executed +sql+ statement.
-
1
def exec_update(sql, name, binds)
-
exec_query(sql, name, binds)
-
end
-
-
# Returns the last auto-generated ID from the affected table.
-
#
-
# +id_value+ will be returned unless the value is nil, in
-
# which case the database will attempt to calculate the last inserted
-
# id and return that value.
-
#
-
# If the next id was calculated in advance (as in Oracle), it should be
-
# passed in as +id_value+.
-
1
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
-
sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
-
value = exec_insert(sql, name, binds, pk, sequence_name)
-
id_value || last_inserted_id(value)
-
end
-
-
# Executes the update statement and returns the number of rows affected.
-
1
def update(arel, name = nil, binds = [])
-
exec_update(to_sql(arel, binds), name, binds)
-
end
-
-
# Executes the delete statement and returns the number of rows affected.
-
1
def delete(arel, name = nil, binds = [])
-
exec_delete(to_sql(arel, binds), name, binds)
-
end
-
-
# Returns +true+ when the connection adapter supports prepared statement
-
# caching, otherwise returns +false+
-
1
def supports_statement_cache?
-
false
-
end
-
-
# Runs the given block in a database transaction, and returns the result
-
# of the block.
-
#
-
# == Nested transactions support
-
#
-
# Most databases don't support true nested transactions. At the time of
-
# writing, the only database that supports true nested transactions that
-
# we're aware of, is MS-SQL.
-
#
-
# In order to get around this problem, #transaction will emulate the effect
-
# of nested transactions, by using savepoints:
-
# http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
-
# Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
-
#
-
# It is safe to call this method if a database transaction is already open,
-
# i.e. if #transaction is called within another #transaction block. In case
-
# of a nested call, #transaction will behave as follows:
-
#
-
# - The block will be run without doing anything. All database statements
-
# that happen within the block are effectively appended to the already
-
# open database transaction.
-
# - However, if +:requires_new+ is set, the block will be wrapped in a
-
# database savepoint acting as a sub-transaction.
-
#
-
# === Caveats
-
#
-
# MySQL doesn't support DDL transactions. If you perform a DDL operation,
-
# then any created savepoints will be automatically released. For example,
-
# if you've created a savepoint, then you execute a CREATE TABLE statement,
-
# then the savepoint that was created will be automatically released.
-
#
-
# This means that, on MySQL, you shouldn't execute DDL operations inside
-
# a #transaction call that you know might create a savepoint. Otherwise,
-
# #transaction will raise exceptions when it tries to release the
-
# already-automatically-released savepoints:
-
#
-
# Model.connection.transaction do # BEGIN
-
# Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
-
# Model.connection.create_table(...)
-
# # active_record_1 now automatically released
-
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
-
# end
-
#
-
# == Transaction isolation
-
#
-
# If your database supports setting the isolation level for a transaction, you can set
-
# it like so:
-
#
-
# Post.transaction(isolation: :serializable) do
-
# # ...
-
# end
-
#
-
# Valid isolation levels are:
-
#
-
# * <tt>:read_uncommitted</tt>
-
# * <tt>:read_committed</tt>
-
# * <tt>:repeatable_read</tt>
-
# * <tt>:serializable</tt>
-
#
-
# You should consult the documentation for your database to understand the
-
# semantics of these different levels:
-
#
-
# * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
-
# * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
-
#
-
# An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if:
-
#
-
# * The adapter does not support setting the isolation level
-
# * You are joining an existing open transaction
-
# * You are creating a nested (savepoint) transaction
-
#
-
# The mysql, mysql2 and postgresql adapters support setting the transaction
-
# isolation level. However, support is disabled for mysql versions below 5,
-
# because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
-
# which means the isolation level gets persisted outside the transaction.
-
1
def transaction(options = {})
-
options.assert_valid_keys :requires_new, :joinable, :isolation
-
-
if !options[:requires_new] && current_transaction.joinable?
-
if options[:isolation]
-
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
-
end
-
-
yield
-
else
-
within_new_transaction(options) { yield }
-
end
-
rescue ActiveRecord::Rollback
-
# rollbacks are silently swallowed
-
end
-
-
1
def within_new_transaction(options = {}) #:nodoc:
-
transaction = begin_transaction(options)
-
yield
-
rescue Exception => error
-
rollback_transaction if transaction
-
raise
-
ensure
-
begin
-
commit_transaction unless error
-
rescue Exception
-
rollback_transaction
-
raise
-
end
-
end
-
-
1
def current_transaction #:nodoc:
-
@transaction
-
end
-
-
1
def transaction_open?
-
@transaction.open?
-
end
-
-
1
def begin_transaction(options = {}) #:nodoc:
-
@transaction = @transaction.begin(options)
-
end
-
-
1
def commit_transaction #:nodoc:
-
@transaction = @transaction.commit
-
end
-
-
1
def rollback_transaction #:nodoc:
-
@transaction = @transaction.rollback
-
end
-
-
1
def reset_transaction #:nodoc:
-
1
@transaction = ClosedTransaction.new(self)
-
end
-
-
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
-
# can be called.
-
1
def add_transaction_record(record)
-
@transaction.add_record(record)
-
end
-
-
# Begins the transaction (and turns off auto-committing).
-
1
def begin_db_transaction() end
-
-
1
def transaction_isolation_levels
-
{
-
read_uncommitted: "READ UNCOMMITTED",
-
read_committed: "READ COMMITTED",
-
repeatable_read: "REPEATABLE READ",
-
serializable: "SERIALIZABLE"
-
}
-
end
-
-
# Begins the transaction with the isolation level set. Raises an error by
-
# default; adapters that support setting the isolation level should implement
-
# this method.
-
1
def begin_isolated_db_transaction(isolation)
-
raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
-
end
-
-
# Commits the transaction (and turns on auto-committing).
-
1
def commit_db_transaction() end
-
-
# Rolls back the transaction (and turns on auto-committing). Must be
-
# done if the transaction block raises an exception or returns false.
-
1
def rollback_db_transaction() end
-
-
1
def default_sequence_name(table, column)
-
nil
-
end
-
-
# Set the sequence to the max value of the table's column.
-
1
def reset_sequence!(table, column, sequence = nil)
-
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
-
end
-
-
# Inserts the given fixture into the table. Overridden in adapters that require
-
# something beyond a simple insert (eg. Oracle).
-
1
def insert_fixture(fixture, table_name)
-
columns = Hash[columns(table_name).map { |c| [c.name, c] }]
-
-
key_list = []
-
value_list = fixture.map do |name, value|
-
key_list << quote_column_name(name)
-
quote(value, columns[name])
-
end
-
-
execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
-
end
-
-
1
def empty_insert_statement_value
-
"DEFAULT VALUES"
-
end
-
-
1
def case_sensitive_equality_operator
-
"="
-
end
-
-
1
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
-
"WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
-
end
-
-
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
-
#
-
# The +limit+ may be anything that can evaluate to a string via #to_s. It
-
# should look like an integer, or a comma-delimited list of integers, or
-
# an Arel SQL literal.
-
#
-
# Returns Integer and Arel::Nodes::SqlLiteral limits as is.
-
# Returns the sanitized limit parameter, either as an integer, or as a
-
# string which contains a comma-delimited list of integers.
-
1
def sanitize_limit(limit)
-
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
-
limit
-
elsif limit.to_s =~ /,/
-
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
-
else
-
Integer(limit)
-
end
-
end
-
-
# The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
-
# on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
-
# an UPDATE statement, so in the mysql adapters we redefine this to do that.
-
1
def join_to_update(update, select) #:nodoc:
-
key = update.key
-
subselect = subquery_for(key, select)
-
-
update.where key.in(subselect)
-
end
-
-
1
def join_to_delete(delete, select, key) #:nodoc:
-
subselect = subquery_for(key, select)
-
-
delete.where key.in(subselect)
-
end
-
-
1
protected
-
-
# Return a subquery for the given key using the join information.
-
1
def subquery_for(key, select)
-
subselect = select.clone
-
subselect.projections = [key]
-
subselect
-
end
-
-
# Returns an array of record hashes with the column names as keys and
-
# column values as values.
-
1
def select(sql, name = nil, binds = [])
-
end
-
1
undef_method :select
-
-
# Returns the last auto-generated ID from the affected table.
-
1
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
-
execute(sql, name)
-
id_value
-
end
-
-
# Executes the update statement and returns the number of rows affected.
-
1
def update_sql(sql, name = nil)
-
execute(sql, name)
-
end
-
-
# Executes the delete statement and returns the number of rows affected.
-
1
def delete_sql(sql, name = nil)
-
update_sql(sql, name)
-
end
-
-
1
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
-
[sql, binds]
-
end
-
-
1
def last_inserted_id(result)
-
row = result.rows.first
-
row && row.first
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters # :nodoc:
-
1
module QueryCache
-
1
class << self
-
1
def included(base) #:nodoc:
-
1
dirties_query_cache base, :insert, :update, :delete
-
end
-
-
1
def dirties_query_cache(base, *method_names)
-
1
method_names.each do |method_name|
-
3
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
-
def #{method_name}(*) # def update_with_query_dirty(*args)
-
clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
-
super # update_without_query_dirty(*args)
-
end # end
-
end_code
-
end
-
end
-
end
-
-
1
attr_reader :query_cache, :query_cache_enabled
-
-
# Enable the query cache within the block.
-
1
def cache
-
old, @query_cache_enabled = @query_cache_enabled, true
-
yield
-
ensure
-
clear_query_cache
-
@query_cache_enabled = old
-
end
-
-
1
def enable_query_cache!
-
@query_cache_enabled = true
-
end
-
-
1
def disable_query_cache!
-
@query_cache_enabled = false
-
end
-
-
# Disable the query cache within the block.
-
1
def uncached
-
old, @query_cache_enabled = @query_cache_enabled, false
-
yield
-
ensure
-
@query_cache_enabled = old
-
end
-
-
# Clears the query cache.
-
#
-
# One reason you may wish to call this method explicitly is between queries
-
# that ask the database to randomize results. Otherwise the cache would see
-
# the same SQL query and repeatedly return the same result each time, silently
-
# undermining the randomness you were expecting.
-
1
def clear_query_cache
-
@query_cache.clear
-
end
-
-
1
def select_all(arel, name = nil, binds = [])
-
if @query_cache_enabled && !locked?(arel)
-
sql = to_sql(arel, binds)
-
cache_sql(sql, binds) { super(sql, name, binds) }
-
else
-
super
-
end
-
end
-
-
1
private
-
-
1
def cache_sql(sql, binds)
-
result =
-
if @query_cache[sql].key?(binds)
-
ActiveSupport::Notifications.instrument("sql.active_record",
-
:sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
-
@query_cache[sql][binds]
-
else
-
@query_cache[sql][binds] = yield
-
end
-
-
# FIXME: we should guarantee that all cached items are Result
-
# objects. Then we can avoid this conditional
-
if ActiveRecord::Result === result
-
result.dup
-
else
-
result.collect { |row| row.dup }
-
end
-
end
-
-
1
def locked?(arel)
-
arel.respond_to?(:locked) && arel.locked
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/big_decimal/conversions'
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters # :nodoc:
-
1
module Quoting
-
# Quotes the column value to help prevent
-
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
-
1
def quote(value, column = nil)
-
# records are quoted as their primary key
-
return value.quoted_id if value.respond_to?(:quoted_id)
-
-
case value
-
when String, ActiveSupport::Multibyte::Chars
-
value = value.to_s
-
return "'#{quote_string(value)}'" unless column
-
-
case column.type
-
when :binary then "'#{quote_string(column.string_to_binary(value))}'"
-
when :integer then value.to_i.to_s
-
when :float then value.to_f.to_s
-
else
-
"'#{quote_string(value)}'"
-
end
-
-
when true, false
-
if column && column.type == :integer
-
value ? '1' : '0'
-
else
-
value ? quoted_true : quoted_false
-
end
-
# BigDecimals need to be put in a non-normalized form and quoted.
-
when nil then "NULL"
-
when BigDecimal then value.to_s('F')
-
when Numeric, ActiveSupport::Duration then value.to_s
-
when Date, Time then "'#{quoted_date(value)}'"
-
when Symbol then "'#{quote_string(value.to_s)}'"
-
when Class then "'#{value.to_s}'"
-
else
-
"'#{quote_string(YAML.dump(value))}'"
-
end
-
end
-
-
# Cast a +value+ to a type that the database understands. For example,
-
# SQLite does not understand dates, so this method will convert a Date
-
# to a String.
-
1
def type_cast(value, column)
-
return value.id if value.respond_to?(:quoted_id)
-
-
case value
-
when String, ActiveSupport::Multibyte::Chars
-
value = value.to_s
-
return value unless column
-
-
case column.type
-
when :binary then value
-
when :integer then value.to_i
-
when :float then value.to_f
-
else
-
value
-
end
-
-
when true, false
-
if column && column.type == :integer
-
value ? 1 : 0
-
else
-
value ? 't' : 'f'
-
end
-
# BigDecimals need to be put in a non-normalized form and quoted.
-
when nil then nil
-
when BigDecimal then value.to_s('F')
-
when Numeric then value
-
when Date, Time then quoted_date(value)
-
when Symbol then value.to_s
-
else
-
to_type = column ? " to #{column.type}" : ""
-
raise TypeError, "can't cast #{value.class}#{to_type}"
-
end
-
end
-
-
# Quotes a string, escaping any ' (single quote) and \ (backslash)
-
# characters.
-
1
def quote_string(s)
-
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
-
end
-
-
# Quotes the column name. Defaults to no quoting.
-
1
def quote_column_name(column_name)
-
column_name
-
end
-
-
# Quotes the table name. Defaults to column name quoting.
-
1
def quote_table_name(table_name)
-
quote_column_name(table_name)
-
end
-
-
1
def quoted_true
-
"'t'"
-
end
-
-
1
def quoted_false
-
"'f'"
-
end
-
-
1
def quoted_date(value)
-
if value.acts_like?(:time)
-
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
-
-
if value.respond_to?(zone_conversion_method)
-
value = value.send(zone_conversion_method)
-
end
-
end
-
-
value.to_s(:db)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters # :nodoc:
-
# The goal of this module is to move Adapter specific column
-
# definitions to the Adapter instead of having it in the schema
-
# dumper itself. This code represents the normal case.
-
# We can then redefine how certain data types may be handled in the schema dumper on the
-
# Adapter level by over-writing this code inside the database spececific adapters
-
1
module ColumnDumper
-
1
def column_spec(column, types)
-
spec = prepare_column_options(column, types)
-
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.to_s}: ")}
-
spec
-
end
-
-
# This can be overridden on a Adapter level basis to support other
-
# extended datatypes (Example: Adding an array option in the
-
# PostgreSQLAdapter)
-
1
def prepare_column_options(column, types)
-
spec = {}
-
spec[:name] = column.name.inspect
-
-
# AR has an optimization which handles zero-scale decimals as integers. This
-
# code ensures that the dumper still dumps the column as a decimal.
-
spec[:type] = if column.type == :integer && /^(numeric|decimal)/ =~ column.sql_type
-
'decimal'
-
else
-
column.type.to_s
-
end
-
spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit] && spec[:type] != 'decimal'
-
spec[:precision] = column.precision.inspect if column.precision
-
spec[:scale] = column.scale.inspect if column.scale
-
spec[:null] = 'false' unless column.null
-
spec[:default] = default_string(column.default) if column.has_default?
-
spec
-
end
-
-
# Lists the valid migration options
-
1
def migration_keys
-
[:name, :limit, :precision, :scale, :default, :null]
-
end
-
-
1
private
-
-
1
def default_string(value)
-
case value
-
when BigDecimal
-
value.to_s
-
when Date, DateTime, Time
-
"'#{value.to_s(:db)}'"
-
else
-
value.inspect
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_record/migration/join_table'
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters # :nodoc:
-
1
module SchemaStatements
-
1
include ActiveRecord::Migration::JoinTable
-
-
# Returns a Hash of mappings from the abstract data types to the native
-
# database types. See TableDefinition#column for details on the recognized
-
# abstract data types.
-
1
def native_database_types
-
{}
-
end
-
-
# Truncates a table alias according to the limits of the current adapter.
-
1
def table_alias_for(table_name)
-
table_name[0...table_alias_length].tr('.', '_')
-
end
-
-
# Checks to see if the table +table_name+ exists on the database.
-
#
-
# table_exists?(:developers)
-
1
def table_exists?(table_name)
-
tables.include?(table_name.to_s)
-
end
-
-
# Returns an array of indexes for the given table.
-
# def indexes(table_name, name = nil) end
-
-
# Checks to see if an index exists on a table for a given index definition.
-
#
-
# # Check an index exists
-
# index_exists?(:suppliers, :company_id)
-
#
-
# # Check an index on multiple columns exists
-
# index_exists?(:suppliers, [:company_id, :company_type])
-
#
-
# # Check a unique index exists
-
# index_exists?(:suppliers, :company_id, unique: true)
-
#
-
# # Check an index with a custom name exists
-
# index_exists?(:suppliers, :company_id, name: "idx_company_id"
-
1
def index_exists?(table_name, column_name, options = {})
-
column_names = Array(column_name)
-
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
-
if options[:unique]
-
indexes(table_name).any?{ |i| i.unique && i.name == index_name }
-
else
-
indexes(table_name).any?{ |i| i.name == index_name }
-
end
-
end
-
-
# Returns an array of Column objects for the table specified by +table_name+.
-
# See the concrete implementation for details on the expected parameter values.
-
1
def columns(table_name) end
-
-
# Checks to see if a column exists in a given table.
-
#
-
# # Check a column exists
-
# column_exists?(:suppliers, :name)
-
#
-
# # Check a column exists of a particular type
-
# column_exists?(:suppliers, :name, :string)
-
#
-
# # Check a column exists with a specific definition
-
# column_exists?(:suppliers, :name, :string, limit: 100)
-
# column_exists?(:suppliers, :name, :string, default: 'default')
-
# column_exists?(:suppliers, :name, :string, null: false)
-
# column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
-
1
def column_exists?(table_name, column_name, type = nil, options = {})
-
columns(table_name).any?{ |c| c.name == column_name.to_s &&
-
(!type || c.type == type) &&
-
(!options.key?(:limit) || c.limit == options[:limit]) &&
-
(!options.key?(:precision) || c.precision == options[:precision]) &&
-
(!options.key?(:scale) || c.scale == options[:scale]) &&
-
(!options.key?(:default) || c.default == options[:default]) &&
-
(!options.key?(:null) || c.null == options[:null]) }
-
end
-
-
# Creates a new table with the name +table_name+. +table_name+ may either
-
# be a String or a Symbol.
-
#
-
# There are two ways to work with +create_table+. You can use the block
-
# form or the regular form, like this:
-
#
-
# === Block form
-
# # create_table() passes a TableDefinition object to the block.
-
# # This form will not only create the table, but also columns for the
-
# # table.
-
#
-
# create_table(:suppliers) do |t|
-
# t.column :name, :string, limit: 60
-
# # Other fields here
-
# end
-
#
-
# === Block form, with shorthand
-
# # You can also use the column types as method calls, rather than calling the column method.
-
# create_table(:suppliers) do |t|
-
# t.string :name, limit: 60
-
# # Other fields here
-
# end
-
#
-
# === Regular form
-
# # Creates a table called 'suppliers' with no columns.
-
# create_table(:suppliers)
-
# # Add a column to 'suppliers'.
-
# add_column(:suppliers, :name, :string, {limit: 60})
-
#
-
# The +options+ hash can include the following keys:
-
# [<tt>:id</tt>]
-
# Whether to automatically add a primary key column. Defaults to true.
-
# Join tables for +has_and_belongs_to_many+ should set it to false.
-
# [<tt>:primary_key</tt>]
-
# The name of the primary key, if one is to be added automatically.
-
# Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
-
#
-
# Also note that this just sets the primary key in the table. You additionally
-
# need to configure the primary key in the model via +self.primary_key=+.
-
# Models do NOT auto-detect the primary key from their table definition.
-
#
-
# [<tt>:options</tt>]
-
# Any extra options you want appended to the table definition.
-
# [<tt>:temporary</tt>]
-
# Make a temporary table.
-
# [<tt>:force</tt>]
-
# Set to true to drop the table before creating it.
-
# Defaults to false.
-
#
-
# ====== Add a backend specific option to the generated SQL (MySQL)
-
# create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
-
# generates:
-
# CREATE TABLE suppliers (
-
# id int(11) DEFAULT NULL auto_increment PRIMARY KEY
-
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
-
#
-
# ====== Rename the primary key column
-
# create_table(:objects, primary_key: 'guid') do |t|
-
# t.column :name, :string, limit: 80
-
# end
-
# generates:
-
# CREATE TABLE objects (
-
# guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
-
# name varchar(80)
-
# )
-
#
-
# ====== Do not add a primary key column
-
# create_table(:categories_suppliers, id: false) do |t|
-
# t.column :category_id, :integer
-
# t.column :supplier_id, :integer
-
# end
-
# generates:
-
# CREATE TABLE categories_suppliers (
-
# category_id int,
-
# supplier_id int
-
# )
-
#
-
# See also TableDefinition#column for details on how to create columns.
-
1
def create_table(table_name, options = {})
-
td = table_definition
-
td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
-
-
yield td if block_given?
-
-
if options[:force] && table_exists?(table_name)
-
drop_table(table_name, options)
-
end
-
-
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
-
create_sql << "#{quote_table_name(table_name)} ("
-
create_sql << td.to_sql
-
create_sql << ") #{options[:options]}"
-
execute create_sql
-
td.indexes.each_pair { |c,o| add_index table_name, c, o }
-
end
-
-
# Creates a new join table with the name created using the lexical order of the first two
-
# arguments. These arguments can be a String or a Symbol.
-
#
-
# # Creates a table called 'assemblies_parts' with no id.
-
# create_join_table(:assemblies, :parts)
-
#
-
# You can pass a +options+ hash can include the following keys:
-
# [<tt>:table_name</tt>]
-
# Sets the table name overriding the default
-
# [<tt>:column_options</tt>]
-
# Any extra options you want appended to the columns definition.
-
# [<tt>:options</tt>]
-
# Any extra options you want appended to the table definition.
-
# [<tt>:temporary</tt>]
-
# Make a temporary table.
-
# [<tt>:force</tt>]
-
# Set to true to drop the table before creating it.
-
# Defaults to false.
-
#
-
# ====== Add a backend specific option to the generated SQL (MySQL)
-
# create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
-
# generates:
-
# CREATE TABLE assemblies_parts (
-
# assembly_id int NOT NULL,
-
# part_id int NOT NULL,
-
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
-
1
def create_join_table(table_1, table_2, options = {})
-
join_table_name = find_join_table_name(table_1, table_2, options)
-
-
column_options = options.delete(:column_options) || {}
-
column_options.reverse_merge!(null: false)
-
-
t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
-
-
create_table(join_table_name, options.merge!(id: false)) do |td|
-
td.integer t1_column, column_options
-
td.integer t2_column, column_options
-
yield td if block_given?
-
end
-
end
-
-
# A block for changing columns in +table+.
-
#
-
# # change_table() yields a Table instance
-
# change_table(:suppliers) do |t|
-
# t.column :name, :string, limit: 60
-
# # Other column alterations here
-
# end
-
#
-
# The +options+ hash can include the following keys:
-
# [<tt>:bulk</tt>]
-
# Set this to true to make this a bulk alter query, such as
-
# ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
-
#
-
# Defaults to false.
-
#
-
# ====== Add a column
-
# change_table(:suppliers) do |t|
-
# t.column :name, :string, limit: 60
-
# end
-
#
-
# ====== Add 2 integer columns
-
# change_table(:suppliers) do |t|
-
# t.integer :width, :height, null: false, default: 0
-
# end
-
#
-
# ====== Add created_at/updated_at columns
-
# change_table(:suppliers) do |t|
-
# t.timestamps
-
# end
-
#
-
# ====== Add a foreign key column
-
# change_table(:suppliers) do |t|
-
# t.references :company
-
# end
-
#
-
# Creates a <tt>company_id(integer)</tt> column
-
#
-
# ====== Add a polymorphic foreign key column
-
# change_table(:suppliers) do |t|
-
# t.belongs_to :company, polymorphic: true
-
# end
-
#
-
# Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
-
#
-
# ====== Remove a column
-
# change_table(:suppliers) do |t|
-
# t.remove :company
-
# end
-
#
-
# ====== Remove several columns
-
# change_table(:suppliers) do |t|
-
# t.remove :company_id
-
# t.remove :width, :height
-
# end
-
#
-
# ====== Remove an index
-
# change_table(:suppliers) do |t|
-
# t.remove_index :company_id
-
# end
-
#
-
# See also Table for details on
-
# all of the various column transformation
-
1
def change_table(table_name, options = {})
-
if supports_bulk_alter? && options[:bulk]
-
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
-
yield Table.new(table_name, recorder)
-
bulk_change_table(table_name, recorder.commands)
-
else
-
yield Table.new(table_name, self)
-
end
-
end
-
-
# Renames a table.
-
#
-
# rename_table('octopuses', 'octopi')
-
1
def rename_table(table_name, new_name)
-
raise NotImplementedError, "rename_table is not implemented"
-
end
-
-
# Drops a table from the database.
-
1
def drop_table(table_name, options = {})
-
execute "DROP TABLE #{quote_table_name(table_name)}"
-
end
-
-
# Adds a new column to the named table.
-
# See TableDefinition#column for details of the options you can use.
-
1
def add_column(table_name, column_name, type, options = {})
-
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
-
add_column_options!(add_column_sql, options)
-
execute(add_column_sql)
-
end
-
-
# Removes the column(s) from the table definition.
-
#
-
# remove_column(:suppliers, :qualification)
-
# remove_columns(:suppliers, :qualification, :experience)
-
1
def remove_column(table_name, *column_names)
-
columns_for_remove(table_name, *column_names).each {|column_name| execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}" }
-
end
-
1
alias :remove_columns :remove_column
-
-
# Changes the column's definition according to the new options.
-
# See TableDefinition#column for details of the options you can use.
-
#
-
# change_column(:suppliers, :name, :string, limit: 80)
-
# change_column(:accounts, :description, :text)
-
1
def change_column(table_name, column_name, type, options = {})
-
raise NotImplementedError, "change_column is not implemented"
-
end
-
-
# Sets a new default value for a column.
-
#
-
# change_column_default(:suppliers, :qualification, 'new')
-
# change_column_default(:accounts, :authorized, 1)
-
# change_column_default(:users, :email, nil)
-
1
def change_column_default(table_name, column_name, default)
-
raise NotImplementedError, "change_column_default is not implemented"
-
end
-
-
# Renames a column.
-
#
-
# rename_column(:suppliers, :description, :name)
-
1
def rename_column(table_name, column_name, new_column_name)
-
raise NotImplementedError, "rename_column is not implemented"
-
end
-
-
# Adds a new index to the table. +column_name+ can be a single Symbol, or
-
# an Array of Symbols.
-
#
-
# The index will be named after the table and the column name(s), unless
-
# you pass <tt>:name</tt> as an option.
-
#
-
# ====== Creating a simple index
-
# add_index(:suppliers, :name)
-
# generates
-
# CREATE INDEX suppliers_name_index ON suppliers(name)
-
#
-
# ====== Creating a unique index
-
# add_index(:accounts, [:branch_id, :party_id], unique: true)
-
# generates
-
# CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
-
#
-
# ====== Creating a named index
-
# add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
-
# generates
-
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
-
#
-
# ====== Creating an index with specific key length
-
# add_index(:accounts, :name, name: 'by_name', length: 10)
-
# generates
-
# CREATE INDEX by_name ON accounts(name(10))
-
#
-
# add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
-
# generates
-
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
-
#
-
# Note: SQLite doesn't support index length
-
#
-
# ====== Creating an index with a sort order (desc or asc, asc is the default)
-
# add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
-
# generates
-
# CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
-
#
-
# Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
-
#
-
# ====== Creating a partial index
-
# add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
-
# generates
-
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
-
#
-
# Note: only supported by PostgreSQL
-
#
-
1
def add_index(table_name, column_name, options = {})
-
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
-
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
-
end
-
-
# Remove the given index from the table.
-
#
-
# Remove the index_accounts_on_column in the accounts table.
-
# remove_index :accounts, :column
-
# Remove the index named index_accounts_on_branch_id in the accounts table.
-
# remove_index :accounts, column: :branch_id
-
# Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table.
-
# remove_index :accounts, column: [:branch_id, :party_id]
-
# Remove the index named by_branch_party in the accounts table.
-
# remove_index :accounts, name: :by_branch_party
-
1
def remove_index(table_name, options = {})
-
remove_index!(table_name, index_name_for_remove(table_name, options))
-
end
-
-
1
def remove_index!(table_name, index_name) #:nodoc:
-
execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
-
end
-
-
# Rename an index.
-
#
-
# Rename the index_people_on_last_name index to index_users_on_last_name
-
# rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
-
1
def rename_index(table_name, old_name, new_name)
-
# this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
-
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
-
return unless old_index_def
-
remove_index(table_name, :name => old_name)
-
add_index(table_name, old_index_def.columns, :name => new_name, :unique => old_index_def.unique)
-
end
-
-
1
def index_name(table_name, options) #:nodoc:
-
if Hash === options
-
if options[:column]
-
"index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
-
elsif options[:name]
-
options[:name]
-
else
-
raise ArgumentError, "You must specify the index name"
-
end
-
else
-
index_name(table_name, :column => options)
-
end
-
end
-
-
# Verify the existence of an index with a given name.
-
#
-
# The default argument is returned if the underlying implementation does not define the indexes method,
-
# as there's no way to determine the correct answer in that case.
-
1
def index_name_exists?(table_name, index_name, default)
-
return default unless respond_to?(:indexes)
-
index_name = index_name.to_s
-
indexes(table_name).detect { |i| i.name == index_name }
-
end
-
-
# Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
-
# <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
-
#
-
# ====== Create a user_id column
-
# add_reference(:products, :user)
-
#
-
# ====== Create a supplier_id and supplier_type columns
-
# add_belongs_to(:products, :supplier, polymorphic: true)
-
#
-
# ====== Create a supplier_id, supplier_type columns and appropriate index
-
# add_reference(:products, :supplier, polymorphic: true, index: true)
-
#
-
1
def add_reference(table_name, ref_name, options = {})
-
polymorphic = options.delete(:polymorphic)
-
index_options = options.delete(:index)
-
add_column(table_name, "#{ref_name}_id", :integer, options)
-
add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
-
add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
-
end
-
1
alias :add_belongs_to :add_reference
-
-
# Removes the reference(s). Also removes a +type+ column if one exists.
-
# <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
-
#
-
# ====== Remove the reference
-
# remove_reference(:products, :user, index: true)
-
#
-
# ====== Remove polymorphic reference
-
# remove_reference(:products, :supplier, polymorphic: true)
-
#
-
1
def remove_reference(table_name, ref_name, options = {})
-
remove_column(table_name, "#{ref_name}_id")
-
remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
-
end
-
1
alias :remove_belongs_to :remove_reference
-
-
# Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
-
# entire structure of the database.
-
1
def structure_dump
-
end
-
-
1
def dump_schema_information #:nodoc:
-
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
-
-
ActiveRecord::SchemaMigration.order('version').map { |sm|
-
"INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
-
}.join "\n\n"
-
end
-
-
# Should not be called normally, but this operation is non-destructive.
-
# The migrations module handles this automatically.
-
1
def initialize_schema_migrations_table
-
ActiveRecord::SchemaMigration.create_table
-
end
-
-
1
def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
-
migrations_paths = Array(migrations_paths)
-
version = version.to_i
-
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
-
-
migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
-
paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
-
versions = Dir[*paths].map do |filename|
-
filename.split('/').last.split('_').first.to_i
-
end
-
-
unless migrated.include?(version)
-
execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
-
end
-
-
inserted = Set.new
-
(versions - migrated).each do |v|
-
if inserted.include?(v)
-
raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
-
elsif v < version
-
execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
-
inserted << v
-
end
-
end
-
end
-
-
1
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
-
if native = native_database_types[type.to_sym]
-
column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
-
-
if type == :decimal # ignore limit, use precision and scale
-
scale ||= native[:scale]
-
-
if precision ||= native[:precision]
-
if scale
-
column_type_sql << "(#{precision},#{scale})"
-
else
-
column_type_sql << "(#{precision})"
-
end
-
elsif scale
-
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
-
end
-
-
elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
-
column_type_sql << "(#{limit})"
-
end
-
-
column_type_sql
-
else
-
type
-
end
-
end
-
-
1
def add_column_options!(sql, options) #:nodoc:
-
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
-
# must explicitly check for :null to allow change_column to work on migrations
-
if options[:null] == false
-
sql << " NOT NULL"
-
end
-
end
-
-
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
-
# Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
-
#
-
# distinct("posts.id", "posts.created_at desc")
-
1
def distinct(columns, order_by)
-
"DISTINCT #{columns}"
-
end
-
-
# Adds timestamps (created_at and updated_at) columns to the named table.
-
#
-
# add_timestamps(:suppliers)
-
1
def add_timestamps(table_name)
-
add_column table_name, :created_at, :datetime
-
add_column table_name, :updated_at, :datetime
-
end
-
-
# Removes the timestamp columns (created_at and updated_at) from the table definition.
-
#
-
# remove_timestamps(:suppliers)
-
1
def remove_timestamps(table_name)
-
remove_column table_name, :updated_at
-
remove_column table_name, :created_at
-
end
-
-
1
protected
-
1
def add_index_sort_order(option_strings, column_names, options = {})
-
if options.is_a?(Hash) && order = options[:order]
-
case order
-
when Hash
-
column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
-
when String
-
column_names.each {|name| option_strings[name] += " #{order.upcase}"}
-
end
-
end
-
-
return option_strings
-
end
-
-
# Overridden by the mysql adapter for supporting index lengths
-
1
def quoted_columns_for_index(column_names, options = {})
-
option_strings = Hash[column_names.map {|name| [name, '']}]
-
-
# add index sort order if supported
-
if supports_index_sort_order?
-
option_strings = add_index_sort_order(option_strings, column_names, options)
-
end
-
-
column_names.map {|name| quote_column_name(name) + option_strings[name]}
-
end
-
-
1
def options_include_default?(options)
-
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
-
end
-
-
1
def add_index_options(table_name, column_name, options = {})
-
column_names = Array(column_name)
-
index_name = index_name(table_name, column: column_names)
-
-
if Hash === options # legacy support, since this param was a string
-
options.assert_valid_keys(:unique, :order, :name, :where, :length)
-
-
index_type = options[:unique] ? "UNIQUE" : ""
-
index_name = options[:name].to_s if options.key?(:name)
-
-
if supports_partial_index?
-
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
-
end
-
else
-
if options
-
message = "Passing a string as third argument of `add_index` is deprecated and will" +
-
" be removed in Rails 4.1." +
-
" Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead"
-
-
ActiveSupport::Deprecation.warn message
-
end
-
-
index_type = options
-
end
-
-
if index_name.length > index_name_length
-
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
-
end
-
if index_name_exists?(table_name, index_name, false)
-
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
-
end
-
index_columns = quoted_columns_for_index(column_names, options).join(", ")
-
-
[index_name, index_type, index_columns, index_options]
-
end
-
-
1
def index_name_for_remove(table_name, options = {})
-
index_name = index_name(table_name, options)
-
-
unless index_name_exists?(table_name, index_name, true)
-
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
-
end
-
-
index_name
-
end
-
-
1
def columns_for_remove(table_name, *column_names)
-
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
-
column_names.map {|column_name| quote_column_name(column_name) }
-
end
-
-
1
private
-
1
def table_definition
-
TableDefinition.new(self)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class Transaction #:nodoc:
-
1
attr_reader :connection
-
-
1
def initialize(connection)
-
1
@connection = connection
-
end
-
end
-
-
1
class ClosedTransaction < Transaction #:nodoc:
-
1
def number
-
0
-
end
-
-
1
def begin(options = {})
-
RealTransaction.new(connection, self, options)
-
end
-
-
1
def closed?
-
true
-
end
-
-
1
def open?
-
false
-
end
-
-
1
def joinable?
-
false
-
end
-
-
# This is a noop when there are no open transactions
-
1
def add_record(record)
-
end
-
end
-
-
1
class OpenTransaction < Transaction #:nodoc:
-
1
attr_reader :parent, :records
-
1
attr_writer :joinable
-
-
1
def initialize(connection, parent, options = {})
-
super connection
-
-
@parent = parent
-
@records = []
-
@finishing = false
-
@joinable = options.fetch(:joinable, true)
-
end
-
-
# This state is necesarry so that we correctly handle stuff that might
-
# happen in a commit/rollback. But it's kinda distasteful. Maybe we can
-
# find a better way to structure it in the future.
-
1
def finishing?
-
@finishing
-
end
-
-
1
def joinable?
-
@joinable && !finishing?
-
end
-
-
1
def number
-
if finishing?
-
parent.number
-
else
-
parent.number + 1
-
end
-
end
-
-
1
def begin(options = {})
-
if finishing?
-
parent.begin
-
else
-
SavepointTransaction.new(connection, self, options)
-
end
-
end
-
-
1
def rollback
-
@finishing = true
-
perform_rollback
-
parent
-
end
-
-
1
def commit
-
@finishing = true
-
perform_commit
-
parent
-
end
-
-
1
def add_record(record)
-
records << record
-
end
-
-
1
def rollback_records
-
records.uniq.each do |record|
-
begin
-
record.rolledback!(parent.closed?)
-
rescue => e
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
-
end
-
end
-
end
-
-
1
def commit_records
-
records.uniq.each do |record|
-
begin
-
record.committed!
-
rescue => e
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
-
end
-
end
-
end
-
-
1
def closed?
-
false
-
end
-
-
1
def open?
-
true
-
end
-
end
-
-
1
class RealTransaction < OpenTransaction #:nodoc:
-
1
def initialize(connection, parent, options = {})
-
super
-
-
if options[:isolation]
-
connection.begin_isolated_db_transaction(options[:isolation])
-
else
-
connection.begin_db_transaction
-
end
-
end
-
-
1
def perform_rollback
-
connection.rollback_db_transaction
-
rollback_records
-
end
-
-
1
def perform_commit
-
connection.commit_db_transaction
-
commit_records
-
end
-
end
-
-
1
class SavepointTransaction < OpenTransaction #:nodoc:
-
1
def initialize(connection, parent, options = {})
-
if options[:isolation]
-
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
-
end
-
-
super
-
connection.create_savepoint
-
end
-
-
1
def perform_rollback
-
connection.rollback_to_savepoint
-
rollback_records
-
end
-
-
1
def perform_commit
-
connection.release_savepoint
-
records.each { |r| parent.add_record(r) }
-
end
-
end
-
end
-
end
-
1
require 'date'
-
1
require 'bigdecimal'
-
1
require 'bigdecimal/util'
-
1
require 'active_support/core_ext/benchmark'
-
1
require 'active_record/connection_adapters/schema_cache'
-
1
require 'active_record/connection_adapters/abstract/schema_dumper'
-
1
require 'monitor'
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters # :nodoc:
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Column
-
1
autoload :ConnectionSpecification
-
-
1
autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do
-
1
autoload :IndexDefinition
-
1
autoload :ColumnDefinition
-
1
autoload :TableDefinition
-
1
autoload :Table
-
end
-
-
1
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
-
1
autoload :ConnectionHandler
-
1
autoload :ConnectionManagement
-
end
-
-
1
autoload_under 'abstract' do
-
1
autoload :SchemaStatements
-
1
autoload :DatabaseStatements
-
1
autoload :DatabaseLimits
-
1
autoload :Quoting
-
1
autoload :ConnectionPool
-
1
autoload :QueryCache
-
end
-
-
1
autoload_at 'active_record/connection_adapters/abstract/transaction' do
-
1
autoload :ClosedTransaction
-
1
autoload :RealTransaction
-
1
autoload :SavepointTransaction
-
end
-
-
# Active Record supports multiple database systems. AbstractAdapter and
-
# related classes form the abstraction layer which makes this possible.
-
# An AbstractAdapter represents a connection to a database, and provides an
-
# abstract interface for database-specific functionality such as establishing
-
# a connection, escaping values, building the right SQL fragments for ':offset'
-
# and ':limit' options, etc.
-
#
-
# All the concrete database adapters follow the interface laid down in this class.
-
# ActiveRecord::Base.connection returns an AbstractAdapter object, which
-
# you can use.
-
#
-
# Most of the methods in the adapter are useful during migrations. Most
-
# notably, the instance methods provided by SchemaStatement are very useful.
-
1
class AbstractAdapter
-
1
include Quoting, DatabaseStatements, SchemaStatements
-
1
include DatabaseLimits
-
1
include QueryCache
-
1
include ActiveSupport::Callbacks
-
1
include MonitorMixin
-
1
include ColumnDumper
-
-
1
define_callbacks :checkout, :checkin
-
-
1
attr_accessor :visitor, :pool
-
1
attr_reader :schema_cache, :last_use, :in_use, :logger
-
1
alias :in_use? :in_use
-
-
1
def initialize(connection, logger = nil, pool = nil) #:nodoc:
-
1
super()
-
-
1
@connection = connection
-
1
@in_use = false
-
1
@instrumenter = ActiveSupport::Notifications.instrumenter
-
1
@last_use = false
-
1
@logger = logger
-
1
@pool = pool
-
1
@query_cache = Hash.new { |h,sql| h[sql] = {} }
-
1
@query_cache_enabled = false
-
1
@schema_cache = SchemaCache.new self
-
1
@visitor = nil
-
end
-
-
1
def lease
-
1
synchronize do
-
1
unless in_use
-
1
@in_use = true
-
1
@last_use = Time.now
-
end
-
end
-
end
-
-
1
def schema_cache=(cache)
-
cache.connection = self
-
@schema_cache = cache
-
end
-
-
1
def expire
-
@in_use = false
-
end
-
-
# Returns the human-readable name of the adapter. Use mixed case - one
-
# can always use downcase if needed.
-
1
def adapter_name
-
'Abstract'
-
end
-
-
# Does this adapter support migrations? Backend specific, as the
-
# abstract adapter always returns +false+.
-
1
def supports_migrations?
-
false
-
end
-
-
# Can this adapter determine the primary key for tables not attached
-
# to an Active Record class, such as join tables? Backend specific, as
-
# the abstract adapter always returns +false+.
-
1
def supports_primary_key?
-
false
-
end
-
-
# Does this adapter support using DISTINCT within COUNT? This is +true+
-
# for all adapters except sqlite.
-
1
def supports_count_distinct?
-
true
-
end
-
-
# Does this adapter support DDL rollbacks in transactions? That is, would
-
# CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
-
# SQL Server, and others support this. MySQL and others do not.
-
1
def supports_ddl_transactions?
-
false
-
end
-
-
1
def supports_bulk_alter?
-
false
-
end
-
-
# Does this adapter support savepoints? PostgreSQL and MySQL do,
-
# SQLite < 3.6.8 does not.
-
1
def supports_savepoints?
-
false
-
end
-
-
# Should primary key values be selected from their corresponding
-
# sequence before the insert statement? If true, next_sequence_value
-
# is called before each insert to set the record's primary key.
-
# This is false for all adapters but Firebird.
-
1
def prefetch_primary_key?(table_name = nil)
-
false
-
end
-
-
# Does this adapter support index sort order?
-
1
def supports_index_sort_order?
-
false
-
end
-
-
# Does this adapter support partial indices?
-
1
def supports_partial_index?
-
false
-
end
-
-
# Does this adapter support explain? As of this writing sqlite3,
-
# mysql2, and postgresql are the only ones that do.
-
1
def supports_explain?
-
false
-
end
-
-
# Does this adapter support setting the isolation level for a transaction?
-
1
def supports_transaction_isolation?
-
false
-
end
-
-
# QUOTING ==================================================
-
-
# Returns a bind substitution value given a +column+ and list of current
-
# +binds+
-
1
def substitute_at(column, index)
-
Arel::Nodes::BindParam.new '?'
-
end
-
-
# REFERENTIAL INTEGRITY ====================================
-
-
# Override to turn off referential integrity while executing <tt>&block</tt>.
-
1
def disable_referential_integrity
-
yield
-
end
-
-
# CONNECTION MANAGEMENT ====================================
-
-
# Checks whether the connection to the database is still active. This includes
-
# checking whether the database is actually capable of responding, i.e. whether
-
# the connection isn't stale.
-
1
def active?
-
end
-
-
# Disconnects from the database if already connected, and establishes a
-
# new connection with the database. Implementors should call super if they
-
# override the default implementation.
-
1
def reconnect!
-
clear_cache!
-
reset_transaction
-
end
-
-
# Disconnects from the database if already connected. Otherwise, this
-
# method does nothing.
-
1
def disconnect!
-
clear_cache!
-
reset_transaction
-
end
-
-
# Reset the state of this connection, directing the DBMS to clear
-
# transactions and other connection-related server-side state. Usually a
-
# database-dependent operation.
-
#
-
# The default implementation does nothing; the implementation should be
-
# overridden by concrete adapters.
-
1
def reset!
-
# this should be overridden by concrete adapters
-
end
-
-
###
-
# Clear any caching the database adapter may be doing, for example
-
# clearing the prepared statement cache. This is database specific.
-
1
def clear_cache!
-
# this should be overridden by concrete adapters
-
end
-
-
# Returns true if its required to reload the connection between requests for development mode.
-
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
-
1
def requires_reloading?
-
false
-
end
-
-
# Checks whether the connection to the database is still active (i.e. not stale).
-
# This is done under the hood by calling <tt>active?</tt>. If the connection
-
# is no longer active, then this method will reconnect to the database.
-
1
def verify!(*ignored)
-
1
reconnect! unless active?
-
end
-
-
# Provides access to the underlying database driver for this adapter. For
-
# example, this method returns a Mysql object in case of MysqlAdapter,
-
# and a PGconn object in case of PostgreSQLAdapter.
-
#
-
# This is useful for when you need to call a proprietary method such as
-
# PostgreSQL's lo_* methods.
-
1
def raw_connection
-
@connection
-
end
-
-
1
def open_transactions
-
@transaction.number
-
end
-
-
1
def increment_open_transactions
-
ActiveSupport::Deprecation.warn "#increment_open_transactions is deprecated and has no effect"
-
end
-
-
1
def decrement_open_transactions
-
ActiveSupport::Deprecation.warn "#decrement_open_transactions is deprecated and has no effect"
-
end
-
-
1
def transaction_joinable=(joinable)
-
message = "#transaction_joinable= is deprecated. Please pass the :joinable option to #begin_transaction instead."
-
ActiveSupport::Deprecation.warn message
-
@transaction.joinable = joinable
-
end
-
-
1
def create_savepoint
-
end
-
-
1
def rollback_to_savepoint
-
end
-
-
1
def release_savepoint
-
end
-
-
1
def case_sensitive_modifier(node)
-
node
-
end
-
-
1
def case_insensitive_comparison(table, attribute, column, value)
-
table[attribute].lower.eq(table.lower(value))
-
end
-
-
1
def current_savepoint_name
-
"active_record_#{open_transactions}"
-
end
-
-
# Check the connection back in to the connection pool
-
1
def close
-
pool.checkin self
-
end
-
-
1
protected
-
-
1
def log(sql, name = "SQL", binds = [])
-
7
@instrumenter.instrument(
-
"sql.active_record",
-
:sql => sql,
-
:name => name,
-
:connection_id => object_id,
-
7
:binds => binds) { yield }
-
rescue => e
-
message = "#{e.class.name}: #{e.message}: #{sql}"
-
@logger.error message if @logger
-
exception = translate_exception(e, message)
-
exception.set_backtrace e.backtrace
-
raise exception
-
end
-
-
1
def translate_exception(exception, message)
-
# override in derived class
-
ActiveRecord::StatementInvalid.new(message)
-
end
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module ActiveRecord
-
# :stopdoc:
-
1
module ConnectionAdapters
-
# An abstract definition of a column in a table.
-
1
class Column
-
1
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
-
1
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
-
-
1
module Format
-
1
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
-
1
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
-
end
-
-
1
attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
-
1
attr_accessor :primary, :coder
-
-
1
alias :encoded? :coder
-
-
# Instantiates a new column in the table.
-
#
-
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
-
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
-
# +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
-
# <tt>company_name varchar(60)</tt>.
-
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
-
# +null+ determines if this column allows +NULL+ values.
-
1
def initialize(name, default, sql_type = nil, null = true)
-
@name = name
-
@sql_type = sql_type
-
@null = null
-
@limit = extract_limit(sql_type)
-
@precision = extract_precision(sql_type)
-
@scale = extract_scale(sql_type)
-
@type = simplified_type(sql_type)
-
@default = extract_default(default)
-
@primary = nil
-
@coder = nil
-
end
-
-
# Returns +true+ if the column is either of type string or text.
-
1
def text?
-
type == :string || type == :text
-
end
-
-
# Returns +true+ if the column is either of type integer, float or decimal.
-
1
def number?
-
type == :integer || type == :float || type == :decimal
-
end
-
-
1
def has_default?
-
!default.nil?
-
end
-
-
# Returns the Ruby class that corresponds to the abstract data type.
-
1
def klass
-
case type
-
when :integer then Fixnum
-
when :float then Float
-
when :decimal then BigDecimal
-
when :datetime, :timestamp, :time then Time
-
when :date then Date
-
when :text, :string, :binary then String
-
when :boolean then Object
-
end
-
end
-
-
1
def binary?
-
type == :binary
-
end
-
-
# Casts a Ruby value to something appropriate for writing to the database.
-
1
def type_cast_for_write(value)
-
return value unless number?
-
-
if value == false
-
0
-
elsif value == true
-
1
-
elsif value.is_a?(String) && value.blank?
-
nil
-
else
-
value
-
end
-
end
-
-
# Casts value (which is a String) to an appropriate instance.
-
1
def type_cast(value)
-
return nil if value.nil?
-
return coder.load(value) if encoded?
-
-
klass = self.class
-
-
case type
-
when :string, :text then value
-
when :integer then klass.value_to_integer(value)
-
when :float then value.to_f
-
when :decimal then klass.value_to_decimal(value)
-
when :datetime, :timestamp then klass.string_to_time(value)
-
when :time then klass.string_to_dummy_time(value)
-
when :date then klass.value_to_date(value)
-
when :binary then klass.binary_to_string(value)
-
when :boolean then klass.value_to_boolean(value)
-
else value
-
end
-
end
-
-
1
def type_cast_code(var_name)
-
message = "Column#type_cast_code is deprecated in favor of using Column#type_cast only, " \
-
"and it is going to be removed in future Rails versions."
-
ActiveSupport::Deprecation.warn message
-
-
klass = self.class.name
-
-
case type
-
when :string, :text then var_name
-
when :integer then "#{klass}.value_to_integer(#{var_name})"
-
when :float then "#{var_name}.to_f"
-
when :decimal then "#{klass}.value_to_decimal(#{var_name})"
-
when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
-
when :time then "#{klass}.string_to_dummy_time(#{var_name})"
-
when :date then "#{klass}.value_to_date(#{var_name})"
-
when :binary then "#{klass}.binary_to_string(#{var_name})"
-
when :boolean then "#{klass}.value_to_boolean(#{var_name})"
-
when :hstore then "#{klass}.string_to_hstore(#{var_name})"
-
when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})"
-
when :json then "#{klass}.string_to_json(#{var_name})"
-
else var_name
-
end
-
end
-
-
# Returns the human name of the column name.
-
#
-
# ===== Examples
-
# Column.new('sales_stage', ...).human_name # => 'Sales stage'
-
1
def human_name
-
Base.human_attribute_name(@name)
-
end
-
-
1
def extract_default(default)
-
type_cast(default)
-
end
-
-
# Used to convert from Strings to BLOBs
-
1
def string_to_binary(value)
-
self.class.string_to_binary(value)
-
end
-
-
1
class << self
-
# Used to convert from Strings to BLOBs
-
1
def string_to_binary(value)
-
value
-
end
-
-
# Used to convert from BLOBs to Strings
-
1
def binary_to_string(value)
-
value
-
end
-
-
1
def value_to_date(value)
-
if value.is_a?(String)
-
return nil if value.blank?
-
fast_string_to_date(value) || fallback_string_to_date(value)
-
elsif value.respond_to?(:to_date)
-
value.to_date
-
else
-
value
-
end
-
end
-
-
1
def string_to_time(string)
-
return string unless string.is_a?(String)
-
return nil if string.blank?
-
-
fast_string_to_time(string) || fallback_string_to_time(string)
-
end
-
-
1
def string_to_dummy_time(string)
-
return string unless string.is_a?(String)
-
return nil if string.blank?
-
-
dummy_time_string = "2000-01-01 #{string}"
-
-
fast_string_to_time(dummy_time_string) || begin
-
time_hash = Date._parse(dummy_time_string)
-
return nil if time_hash[:hour].nil?
-
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
-
end
-
end
-
-
# convert something to a boolean
-
1
def value_to_boolean(value)
-
if value.is_a?(String) && value.blank?
-
nil
-
else
-
TRUE_VALUES.include?(value)
-
end
-
end
-
-
# Used to convert values to integer.
-
# handle the case when an integer column is used to store boolean values
-
1
def value_to_integer(value)
-
case value
-
when TrueClass, FalseClass
-
value ? 1 : 0
-
else
-
value.to_i
-
end
-
end
-
-
# convert something to a BigDecimal
-
1
def value_to_decimal(value)
-
# Using .class is faster than .is_a? and
-
# subclasses of BigDecimal will be handled
-
# in the else clause
-
if value.class == BigDecimal
-
value
-
elsif value.respond_to?(:to_d)
-
value.to_d
-
else
-
value.to_s.to_d
-
end
-
end
-
-
1
protected
-
# '0.123456' -> 123456
-
# '1.123456' -> 123456
-
1
def microseconds(time)
-
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
-
end
-
-
1
def new_date(year, mon, mday)
-
if year && year != 0
-
Date.new(year, mon, mday) rescue nil
-
end
-
end
-
-
1
def new_time(year, mon, mday, hour, min, sec, microsec)
-
# Treat 0000-00-00 00:00:00 as nil.
-
return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
-
-
Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
-
end
-
-
1
def fast_string_to_date(string)
-
if string =~ Format::ISO_DATE
-
new_date $1.to_i, $2.to_i, $3.to_i
-
end
-
end
-
-
# Doesn't handle time zones.
-
1
def fast_string_to_time(string)
-
if string =~ Format::ISO_DATETIME
-
microsec = ($7.to_r * 1_000_000).to_i
-
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
-
end
-
end
-
-
1
def fallback_string_to_date(string)
-
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
-
end
-
-
1
def fallback_string_to_time(string)
-
time_hash = Date._parse(string)
-
time_hash[:sec_fraction] = microseconds(time_hash)
-
-
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
-
end
-
end
-
-
1
private
-
1
def extract_limit(sql_type)
-
$1.to_i if sql_type =~ /\((.*)\)/
-
end
-
-
1
def extract_precision(sql_type)
-
$2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
-
end
-
-
1
def extract_scale(sql_type)
-
case sql_type
-
when /^(numeric|decimal|number)\((\d+)\)/i then 0
-
when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
-
end
-
end
-
-
1
def simplified_type(field_type)
-
case field_type
-
when /int/i
-
:integer
-
when /float|double/i
-
:float
-
when /decimal|numeric|number/i
-
extract_scale(field_type) == 0 ? :integer : :decimal
-
when /datetime/i
-
:datetime
-
when /timestamp/i
-
:timestamp
-
when /time/i
-
:time
-
when /date/i
-
:date
-
when /clob/i, /text/i
-
:text
-
when /blob/i, /binary/i
-
:binary
-
when /char/i, /string/i
-
:string
-
when /boolean/i
-
:boolean
-
end
-
end
-
end
-
end
-
# :startdoc:
-
end
-
1
require 'uri'
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class ConnectionSpecification #:nodoc:
-
1
attr_reader :config, :adapter_method
-
-
1
def initialize(config, adapter_method)
-
1
@config, @adapter_method = config, adapter_method
-
end
-
-
1
def initialize_dup(original)
-
@config = original.config.dup
-
end
-
-
##
-
# Builds a ConnectionSpecification from user input
-
1
class Resolver # :nodoc:
-
1
attr_reader :config, :klass, :configurations
-
-
1
def initialize(config, configurations)
-
1
@config = config
-
1
@configurations = configurations
-
end
-
-
1
def spec
-
1
case config
-
when nil
-
raise AdapterNotSpecified unless defined?(Rails.env)
-
resolve_string_connection Rails.env
-
when Symbol, String
-
resolve_string_connection config.to_s
-
when Hash
-
1
resolve_hash_connection config
-
end
-
end
-
-
1
private
-
1
def resolve_string_connection(spec) # :nodoc:
-
hash = configurations.fetch(spec) do |k|
-
connection_url_to_hash(k)
-
end
-
-
raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
-
-
resolve_hash_connection hash
-
end
-
-
1
def resolve_hash_connection(spec) # :nodoc:
-
1
spec = spec.symbolize_keys
-
-
1
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
-
-
1
begin
-
1
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
-
rescue LoadError => e
-
raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace
-
end
-
-
1
adapter_method = "#{spec[:adapter]}_connection"
-
-
1
ConnectionSpecification.new(spec, adapter_method)
-
end
-
-
1
def connection_url_to_hash(url) # :nodoc:
-
config = URI.parse url
-
adapter = config.scheme
-
adapter = "postgresql" if adapter == "postgres"
-
spec = { :adapter => adapter,
-
:username => config.user,
-
:password => config.password,
-
:port => config.port,
-
:database => config.path.sub(%r{^/},""),
-
:host => config.host }
-
spec.reject!{ |_,value| value.blank? }
-
uri_parser = URI::Parser.new
-
spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) }
-
if config.query
-
options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
-
spec.merge!(options)
-
end
-
spec
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class SchemaCache
-
1
attr_reader :columns, :columns_hash, :primary_keys, :tables, :version
-
1
attr_accessor :connection
-
-
1
def initialize(conn)
-
1
@connection = conn
-
-
1
@columns = {}
-
1
@columns_hash = {}
-
1
@primary_keys = {}
-
1
@tables = {}
-
1
prepare_default_proc
-
end
-
-
# A cached lookup for table existence.
-
1
def table_exists?(name)
-
return @tables[name] if @tables.key? name
-
-
@tables[name] = connection.table_exists?(name)
-
end
-
-
# Add internal cache for table with +table_name+.
-
1
def add(table_name)
-
if table_exists?(table_name)
-
@primary_keys[table_name]
-
@columns[table_name]
-
@columns_hash[table_name]
-
end
-
end
-
-
# Clears out internal caches
-
1
def clear!
-
@columns.clear
-
@columns_hash.clear
-
@primary_keys.clear
-
@tables.clear
-
@version = nil
-
end
-
-
# Clear out internal caches for table with +table_name+.
-
1
def clear_table_cache!(table_name)
-
@columns.delete table_name
-
@columns_hash.delete table_name
-
@primary_keys.delete table_name
-
@tables.delete table_name
-
end
-
-
1
def marshal_dump
-
# if we get current version during initialization, it happens stack over flow.
-
@version = ActiveRecord::Migrator.current_version
-
[@version] + [:@columns, :@columns_hash, :@primary_keys, :@tables].map do |val|
-
self.instance_variable_get(val).inject({}) { |h, v| h[v[0]] = v[1]; h }
-
end
-
end
-
-
1
def marshal_load(array)
-
@version, @columns, @columns_hash, @primary_keys, @tables = array
-
prepare_default_proc
-
end
-
-
1
private
-
-
1
def prepare_default_proc
-
1
@columns.default_proc = Proc.new do |h, table_name|
-
h[table_name] = connection.columns(table_name)
-
end
-
-
1
@columns_hash.default_proc = Proc.new do |h, table_name|
-
h[table_name] = Hash[columns[table_name].map { |col|
-
[col.name, col]
-
}]
-
end
-
-
1
@primary_keys.default_proc = Proc.new do |h, table_name|
-
h[table_name] = table_exists?(table_name) ? connection.primary_key(table_name) : nil
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_record/connection_adapters/abstract_adapter'
-
1
require 'active_record/connection_adapters/statement_pool'
-
1
require 'arel/visitors/bind_visitor'
-
-
1
gem 'sqlite3', '~> 1.3.6'
-
1
require 'sqlite3'
-
-
1
module ActiveRecord
-
1
module ConnectionHandling
-
# sqlite3 adapter reuses sqlite_connection.
-
1
def sqlite3_connection(config) # :nodoc:
-
# Require database.
-
1
unless config[:database]
-
raise ArgumentError, "No database file specified. Missing argument: database"
-
end
-
-
# Allow database path relative to Rails.root, but only if
-
# the database path is not the special path that tells
-
# Sqlite to build a database only in memory.
-
1
if defined?(Rails.root) && ':memory:' != config[:database]
-
config[:database] = File.expand_path(config[:database], Rails.root)
-
end
-
-
1
db = SQLite3::Database.new(
-
config[:database],
-
:results_as_hash => true
-
)
-
-
1
db.busy_timeout(config[:timeout]) if config[:timeout]
-
-
1
ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
-
end
-
end
-
-
1
module ConnectionAdapters #:nodoc:
-
1
class SQLite3Column < Column #:nodoc:
-
1
class << self
-
1
def binary_to_string(value)
-
if value.encoding != Encoding::ASCII_8BIT
-
value = value.force_encoding(Encoding::ASCII_8BIT)
-
end
-
value
-
end
-
end
-
end
-
-
# The SQLite3 adapter works SQLite 3.6.16 or newer
-
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
-
#
-
# Options:
-
#
-
# * <tt>:database</tt> - Path to the database file.
-
1
class SQLite3Adapter < AbstractAdapter
-
1
class Version
-
1
include Comparable
-
-
1
def initialize(version_string)
-
@version = version_string.split('.').map { |v| v.to_i }
-
end
-
-
1
def <=>(version_string)
-
@version <=> version_string.split('.').map { |v| v.to_i }
-
end
-
end
-
-
1
class StatementPool < ConnectionAdapters::StatementPool
-
1
def initialize(connection, max)
-
1
super
-
1
@cache = Hash.new { |h,pid| h[pid] = {} }
-
end
-
-
1
def each(&block); cache.each(&block); end
-
1
def key?(key); cache.key?(key); end
-
1
def [](key); cache[key]; end
-
1
def length; cache.length; end
-
-
1
def []=(sql, key)
-
while @max <= cache.size
-
dealloc(cache.shift.last[:stmt])
-
end
-
cache[sql] = key
-
end
-
-
1
def clear
-
cache.values.each do |hash|
-
dealloc hash[:stmt]
-
end
-
cache.clear
-
end
-
-
1
private
-
1
def cache
-
@cache[$$]
-
end
-
-
1
def dealloc(stmt)
-
stmt.close unless stmt.closed?
-
end
-
end
-
-
1
class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
-
1
include Arel::Visitors::BindVisitor
-
end
-
-
1
def initialize(connection, logger, config)
-
1
super(connection, logger)
-
-
1
@active = nil
-
1
@statements = StatementPool.new(@connection,
-
1
config.fetch(:statement_limit) { 1000 })
-
1
@config = config
-
-
2
if config.fetch(:prepared_statements) { true }
-
1
@visitor = Arel::Visitors::SQLite.new self
-
else
-
@visitor = BindSubstitution.new self
-
end
-
end
-
-
1
def adapter_name #:nodoc:
-
'SQLite'
-
end
-
-
# Returns true
-
1
def supports_ddl_transactions?
-
true
-
end
-
-
# Returns true if SQLite version is '3.6.8' or greater, false otherwise.
-
1
def supports_savepoints?
-
sqlite_version >= '3.6.8'
-
end
-
-
# Returns true, since this connection adapter supports prepared statement
-
# caching.
-
1
def supports_statement_cache?
-
true
-
end
-
-
# Returns true, since this connection adapter supports migrations.
-
1
def supports_migrations? #:nodoc:
-
true
-
end
-
-
# Returns true.
-
1
def supports_primary_key? #:nodoc:
-
true
-
end
-
-
1
def requires_reloading?
-
true
-
end
-
-
# Returns true
-
1
def supports_add_column?
-
true
-
end
-
-
1
def active?
-
1
@active != false
-
end
-
-
# Disconnects from the database if already connected. Otherwise, this
-
# method does nothing.
-
1
def disconnect!
-
super
-
@active = false
-
@connection.close rescue nil
-
end
-
-
# Clears the prepared statements cache.
-
1
def clear_cache!
-
@statements.clear
-
end
-
-
# Returns true
-
1
def supports_count_distinct? #:nodoc:
-
true
-
end
-
-
# Returns true
-
1
def supports_autoincrement? #:nodoc:
-
true
-
end
-
-
1
def supports_index_sort_order?
-
true
-
end
-
-
1
def native_database_types #:nodoc:
-
{
-
:primary_key => default_primary_key_type,
-
:string => { :name => "varchar", :limit => 255 },
-
:text => { :name => "text" },
-
:integer => { :name => "integer" },
-
:float => { :name => "float" },
-
:decimal => { :name => "decimal" },
-
:datetime => { :name => "datetime" },
-
:timestamp => { :name => "datetime" },
-
:time => { :name => "time" },
-
:date => { :name => "date" },
-
:binary => { :name => "blob" },
-
:boolean => { :name => "boolean" }
-
}
-
end
-
-
# Returns the current database encoding format as a string, eg: 'UTF-8'
-
1
def encoding
-
@connection.encoding.to_s
-
end
-
-
# Returns true.
-
1
def supports_explain?
-
true
-
end
-
-
# QUOTING ==================================================
-
-
1
def quote(value, column = nil)
-
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
-
s = column.class.string_to_binary(value).unpack("H*")[0]
-
"x'#{s}'"
-
else
-
super
-
end
-
end
-
-
1
def quote_string(s) #:nodoc:
-
@connection.class.quote(s)
-
end
-
-
1
def quote_column_name(name) #:nodoc:
-
1
%Q("#{name.to_s.gsub('"', '""')}")
-
end
-
-
# Quote date/time values for use in SQL input. Includes microseconds
-
# if the value is a Time responding to usec.
-
1
def quoted_date(value) #:nodoc:
-
if value.respond_to?(:usec)
-
"#{super}.#{sprintf("%06d", value.usec)}"
-
else
-
super
-
end
-
end
-
-
1
def type_cast(value, column) # :nodoc:
-
return value.to_f if BigDecimal === value
-
return super unless String === value
-
return super unless column && value
-
-
value = super
-
if column.type == :string && value.encoding == Encoding::ASCII_8BIT
-
logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
-
value = value.encode Encoding::UTF_8
-
end
-
value
-
end
-
-
# DATABASE STATEMENTS ======================================
-
-
1
def explain(arel, binds = [])
-
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
-
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
-
end
-
-
1
class ExplainPrettyPrinter
-
# Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
-
# the output of the SQLite shell:
-
#
-
# 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
-
# 0|1|1|SCAN TABLE posts (~100000 rows)
-
#
-
1
def pp(result) # :nodoc:
-
result.rows.map do |row|
-
row.join('|')
-
end.join("\n") + "\n"
-
end
-
end
-
-
1
def exec_query(sql, name = nil, binds = [])
-
log(sql, name, binds) do
-
-
# Don't cache statements without bind values
-
if binds.empty?
-
stmt = @connection.prepare(sql)
-
cols = stmt.columns
-
records = stmt.to_a
-
stmt.close
-
stmt = records
-
else
-
cache = @statements[sql] ||= {
-
:stmt => @connection.prepare(sql)
-
}
-
stmt = cache[:stmt]
-
cols = cache[:cols] ||= stmt.columns
-
stmt.reset!
-
stmt.bind_params binds.map { |col, val|
-
type_cast(val, col)
-
}
-
end
-
-
ActiveRecord::Result.new(cols, stmt.to_a)
-
end
-
end
-
-
1
def exec_delete(sql, name = 'SQL', binds = [])
-
exec_query(sql, name, binds)
-
@connection.changes
-
end
-
1
alias :exec_update :exec_delete
-
-
1
def last_inserted_id(result)
-
@connection.last_insert_row_id
-
end
-
-
1
def execute(sql, name = nil) #:nodoc:
-
14
log(sql, name) { @connection.execute(sql) }
-
end
-
-
1
def update_sql(sql, name = nil) #:nodoc:
-
super
-
@connection.changes
-
end
-
-
1
def delete_sql(sql, name = nil) #:nodoc:
-
sql += " WHERE 1=1" unless sql =~ /WHERE/i
-
super sql, name
-
end
-
-
1
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
-
super
-
id_value || @connection.last_insert_row_id
-
end
-
1
alias :create :insert_sql
-
-
1
def select_rows(sql, name = nil)
-
exec_query(sql, name).rows
-
end
-
-
1
def create_savepoint
-
execute("SAVEPOINT #{current_savepoint_name}")
-
end
-
-
1
def rollback_to_savepoint
-
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
-
end
-
-
1
def release_savepoint
-
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
-
end
-
-
1
def begin_db_transaction #:nodoc:
-
log('begin transaction',nil) { @connection.transaction }
-
end
-
-
1
def commit_db_transaction #:nodoc:
-
log('commit transaction',nil) { @connection.commit }
-
end
-
-
1
def rollback_db_transaction #:nodoc:
-
log('rollback transaction',nil) { @connection.rollback }
-
end
-
-
# SCHEMA STATEMENTS ========================================
-
-
1
def tables(name = nil, table_name = nil) #:nodoc:
-
sql = <<-SQL
-
SELECT name
-
FROM sqlite_master
-
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
-
SQL
-
sql << " AND name = #{quote_table_name(table_name)}" if table_name
-
-
exec_query(sql, 'SCHEMA').map do |row|
-
row['name']
-
end
-
end
-
-
1
def table_exists?(table_name)
-
table_name && tables(nil, table_name).any?
-
end
-
-
# Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
-
1
def columns(table_name) #:nodoc:
-
table_structure(table_name).map do |field|
-
case field["dflt_value"]
-
when /^null$/i
-
field["dflt_value"] = nil
-
when /^'(.*)'$/m
-
field["dflt_value"] = $1.gsub("''", "'")
-
when /^"(.*)"$/m
-
field["dflt_value"] = $1.gsub('""', '"')
-
end
-
-
SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
-
end
-
end
-
-
# Returns an array of indexes for the given table.
-
1
def indexes(table_name, name = nil) #:nodoc:
-
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
-
IndexDefinition.new(
-
table_name,
-
row['name'],
-
row['unique'] != 0,
-
exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
-
col['name']
-
})
-
end
-
end
-
-
1
def primary_key(table_name) #:nodoc:
-
column = table_structure(table_name).find { |field|
-
field['pk'] == 1
-
}
-
column && column['name']
-
end
-
-
1
def remove_index!(table_name, index_name) #:nodoc:
-
exec_query "DROP INDEX #{quote_column_name(index_name)}"
-
end
-
-
# Renames a table.
-
#
-
# Example:
-
# rename_table('octopuses', 'octopi')
-
1
def rename_table(name, new_name)
-
exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
-
end
-
-
# See: http://www.sqlite.org/lang_altertable.html
-
# SQLite has an additional restriction on the ALTER TABLE statement
-
1
def valid_alter_table_options( type, options)
-
type.to_sym != :primary_key
-
end
-
-
1
def add_column(table_name, column_name, type, options = {}) #:nodoc:
-
if supports_add_column? && valid_alter_table_options( type, options )
-
super(table_name, column_name, type, options)
-
else
-
alter_table(table_name) do |definition|
-
definition.column(column_name, type, options)
-
end
-
end
-
end
-
-
1
def remove_column(table_name, *column_names) #:nodoc:
-
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
-
column_names.each do |column_name|
-
alter_table(table_name) do |definition|
-
definition.columns.delete(definition[column_name])
-
end
-
end
-
end
-
1
alias :remove_columns :remove_column
-
-
1
def change_column_default(table_name, column_name, default) #:nodoc:
-
alter_table(table_name) do |definition|
-
definition[column_name].default = default
-
end
-
end
-
-
1
def change_column_null(table_name, column_name, null, default = nil)
-
unless null || default.nil?
-
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
-
end
-
alter_table(table_name) do |definition|
-
definition[column_name].null = null
-
end
-
end
-
-
1
def change_column(table_name, column_name, type, options = {}) #:nodoc:
-
alter_table(table_name) do |definition|
-
include_default = options_include_default?(options)
-
definition[column_name].instance_eval do
-
self.type = type
-
self.limit = options[:limit] if options.include?(:limit)
-
self.default = options[:default] if include_default
-
self.null = options[:null] if options.include?(:null)
-
self.precision = options[:precision] if options.include?(:precision)
-
self.scale = options[:scale] if options.include?(:scale)
-
end
-
end
-
end
-
-
1
def rename_column(table_name, column_name, new_column_name) #:nodoc:
-
unless columns(table_name).detect{|c| c.name == column_name.to_s }
-
raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
-
end
-
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
-
end
-
-
1
protected
-
1
def select(sql, name = nil, binds = []) #:nodoc:
-
exec_query(sql, name, binds)
-
end
-
-
1
def table_structure(table_name)
-
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
-
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
-
structure
-
end
-
-
1
def alter_table(table_name, options = {}) #:nodoc:
-
altered_table_name = "altered_#{table_name}"
-
caller = lambda {|definition| yield definition if block_given?}
-
-
transaction do
-
move_table(table_name, altered_table_name,
-
options.merge(:temporary => true))
-
move_table(altered_table_name, table_name, &caller)
-
end
-
end
-
-
1
def move_table(from, to, options = {}, &block) #:nodoc:
-
copy_table(from, to, options, &block)
-
drop_table(from)
-
end
-
-
1
def copy_table(from, to, options = {}) #:nodoc:
-
from_primary_key = primary_key(from)
-
options[:id] = false
-
create_table(to, options) do |definition|
-
@definition = definition
-
@definition.primary_key(from_primary_key) if from_primary_key.present?
-
columns(from).each do |column|
-
column_name = options[:rename] ?
-
(options[:rename][column.name] ||
-
options[:rename][column.name.to_sym] ||
-
column.name) : column.name
-
next if column_name == from_primary_key
-
-
@definition.column(column_name, column.type,
-
:limit => column.limit, :default => column.default,
-
:precision => column.precision, :scale => column.scale,
-
:null => column.null)
-
end
-
yield @definition if block_given?
-
end
-
-
copy_table_indexes(from, to, options[:rename] || {})
-
copy_table_contents(from, to,
-
@definition.columns.map {|column| column.name},
-
options[:rename] || {})
-
end
-
-
1
def copy_table_indexes(from, to, rename = {}) #:nodoc:
-
indexes(from).each do |index|
-
name = index.name
-
if to == "altered_#{from}"
-
name = "temp_#{name}"
-
elsif from == "altered_#{to}"
-
name = name[5..-1]
-
end
-
-
to_column_names = columns(to).map { |c| c.name }
-
columns = index.columns.map {|c| rename[c] || c }.select do |column|
-
to_column_names.include?(column)
-
end
-
-
unless columns.empty?
-
# index name can't be the same
-
opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
-
opts[:unique] = true if index.unique
-
add_index(to, columns, opts)
-
end
-
end
-
end
-
-
1
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
-
column_mappings = Hash[columns.map {|name| [name, name]}]
-
rename.each { |a| column_mappings[a.last] = a.first }
-
from_columns = columns(from).collect {|col| col.name}
-
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
-
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
-
-
quoted_to = quote_table_name(to)
-
exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
-
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
-
sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
-
sql << ')'
-
exec_query sql
-
end
-
end
-
-
1
def sqlite_version
-
@sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
-
end
-
-
1
def default_primary_key_type
-
if supports_autoincrement?
-
'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
-
else
-
'INTEGER PRIMARY KEY NOT NULL'
-
end
-
end
-
-
1
def translate_exception(exception, message)
-
case exception.message
-
when /column(s)? .* (is|are) not unique/
-
RecordNotUnique.new(message, exception)
-
else
-
super
-
end
-
end
-
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class StatementPool
-
1
include Enumerable
-
-
1
def initialize(connection, max = 1000)
-
1
@connection = connection
-
1
@max = max
-
end
-
-
1
def each
-
raise NotImplementedError
-
end
-
-
1
def key?(key)
-
raise NotImplementedError
-
end
-
-
1
def [](key)
-
raise NotImplementedError
-
end
-
-
1
def length
-
raise NotImplementedError
-
end
-
-
1
def []=(sql, key)
-
raise NotImplementedError
-
end
-
-
1
def clear
-
raise NotImplementedError
-
end
-
-
1
def delete(key)
-
raise NotImplementedError
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionHandling
-
# Establishes the connection to the database. Accepts a hash as input where
-
# the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
-
# example for regular databases (MySQL, Postgresql, etc):
-
#
-
# ActiveRecord::Base.establish_connection(
-
# :adapter => "mysql",
-
# :host => "localhost",
-
# :username => "myuser",
-
# :password => "mypass",
-
# :database => "somedatabase"
-
# )
-
#
-
# Example for SQLite database:
-
#
-
# ActiveRecord::Base.establish_connection(
-
# :adapter => "sqlite",
-
# :database => "path/to/dbfile"
-
# )
-
#
-
# Also accepts keys as strings (for parsing from YAML for example):
-
#
-
# ActiveRecord::Base.establish_connection(
-
# "adapter" => "sqlite",
-
# "database" => "path/to/dbfile"
-
# )
-
#
-
# Or a URL:
-
#
-
# ActiveRecord::Base.establish_connection(
-
# "postgres://myuser:mypass@localhost/somedatabase"
-
# )
-
#
-
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
-
# may be returned on an error.
-
1
def establish_connection(spec = ENV["DATABASE_URL"])
-
1
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new spec, configurations
-
1
spec = resolver.spec
-
-
1
unless respond_to?(spec.adapter_method)
-
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
-
end
-
-
1
remove_connection
-
1
connection_handler.establish_connection self, spec
-
end
-
-
# Returns the connection currently associated with the class. This can
-
# also be used to "borrow" the connection to do database work unrelated
-
# to any of the specific Active Records.
-
1
def connection
-
9
retrieve_connection
-
end
-
-
1
def connection_id
-
9
Thread.current['ActiveRecord::Base.connection_id']
-
end
-
-
1
def connection_id=(connection_id)
-
1
Thread.current['ActiveRecord::Base.connection_id'] = connection_id
-
end
-
-
# Returns the configuration of the associated connection as a hash:
-
#
-
# ActiveRecord::Base.connection_config
-
# # => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"}
-
#
-
# Please use only for reading.
-
1
def connection_config
-
connection_pool.spec.config
-
end
-
-
1
def connection_pool
-
connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
-
end
-
-
1
def retrieve_connection
-
9
connection_handler.retrieve_connection(self)
-
end
-
-
# Returns true if Active Record is connected.
-
1
def connected?
-
connection_handler.connected?(self)
-
end
-
-
1
def remove_connection(klass = self)
-
1
connection_handler.remove_connection(klass)
-
end
-
-
1
def clear_cache! # :nodoc:
-
connection.schema_cache.clear!
-
end
-
-
1
delegate :clear_active_connections!, :clear_reloadable_connections!,
-
:clear_all_connections!, :to => :connection_handler
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'thread'
-
-
1
module ActiveRecord
-
1
module Core
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
##
-
# :singleton-method:
-
#
-
# Accepts a logger conforming to the interface of Log4r which is then
-
# passed on to any new database connections made and which can be
-
# retrieved on both a class and instance level by calling +logger+.
-
1
mattr_accessor :logger, instance_writer: false
-
-
##
-
# :singleton-method:
-
# Contains the database configuration - as is typically stored in config/database.yml -
-
# as a Hash.
-
#
-
# For example, the following database.yml...
-
#
-
# development:
-
# adapter: sqlite3
-
# database: db/development.sqlite3
-
#
-
# production:
-
# adapter: sqlite3
-
# database: db/production.sqlite3
-
#
-
# ...would result in ActiveRecord::Base.configurations to look like this:
-
#
-
# {
-
# 'development' => {
-
# 'adapter' => 'sqlite3',
-
# 'database' => 'db/development.sqlite3'
-
# },
-
# 'production' => {
-
# 'adapter' => 'sqlite3',
-
# 'database' => 'db/production.sqlite3'
-
# }
-
# }
-
1
mattr_accessor :configurations, instance_writer: false
-
1
self.configurations = {}
-
-
##
-
# :singleton-method:
-
# Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
-
# dates and times from the database. This is set to :utc by default.
-
1
mattr_accessor :default_timezone, instance_writer: false
-
1
self.default_timezone = :utc
-
-
##
-
# :singleton-method:
-
# Specifies the format to use when dumping the database schema with Rails'
-
# Rakefile. If :sql, the schema is dumped as (potentially database-
-
# specific) SQL statements. If :ruby, the schema is dumped as an
-
# ActiveRecord::Schema file which can be loaded into any database that
-
# supports migrations. Use :ruby if you want to have different database
-
# adapters for, e.g., your development and test environments.
-
1
mattr_accessor :schema_format, instance_writer: false
-
1
self.schema_format = :ruby
-
-
##
-
# :singleton-method:
-
# Specify whether or not to use timestamps for migration versions
-
1
mattr_accessor :timestamped_migrations, instance_writer: false
-
1
self.timestamped_migrations = true
-
-
1
class_attribute :connection_handler, instance_writer: false
-
1
self.connection_handler = ConnectionAdapters::ConnectionHandler.new
-
end
-
-
1
module ClassMethods
-
1
def inherited(child_class) #:nodoc:
-
7
child_class.initialize_generated_modules
-
7
super
-
end
-
-
1
def initialize_generated_modules
-
7
@attribute_methods_mutex = Mutex.new
-
-
# force attribute methods to be higher in inheritance hierarchy than other generated methods
-
7
generated_attribute_methods
-
7
generated_feature_methods
-
end
-
-
1
def generated_feature_methods
-
@generated_feature_methods ||= begin
-
7
mod = const_set(:GeneratedFeatureMethods, Module.new)
-
7
include mod
-
7
mod
-
40
end
-
end
-
-
# Returns a string like 'Post(id:integer, title:string, body:text)'
-
1
def inspect
-
if self == Base
-
super
-
elsif abstract_class?
-
"#{super}(abstract)"
-
elsif table_exists?
-
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
-
"#{super}(#{attr_list})"
-
else
-
"#{super}(Table doesn't exist)"
-
end
-
end
-
-
# Overwrite the default class equality method to provide support for association proxies.
-
1
def ===(object)
-
object.is_a?(self)
-
end
-
-
# Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
-
#
-
# class Post < ActiveRecord::Base
-
# scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0))
-
# end
-
1
def arel_table
-
1
@arel_table ||= Arel::Table.new(table_name, arel_engine)
-
end
-
-
# Returns the Arel engine.
-
1
def arel_engine
-
@arel_engine ||= begin
-
1
if Base == self || connection_handler.retrieve_connection_pool(self)
-
1
self
-
else
-
superclass.arel_engine
-
end
-
1
end
-
end
-
-
1
private
-
-
1
def relation #:nodoc:
-
relation = Relation.new(self, arel_table)
-
-
if finder_needs_type_condition?
-
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
-
else
-
relation
-
end
-
end
-
end
-
-
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
-
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
-
# In both instances, valid attribute keys are determined by the column names of the associated table --
-
# hence you can't have attributes that aren't part of the table columns.
-
#
-
# ==== Example:
-
# # Instantiates a single new object
-
# User.new(:first_name => 'Jamie')
-
1
def initialize(attributes = nil)
-
defaults = self.class.column_defaults.dup
-
defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
-
-
@attributes = self.class.initialize_attributes(defaults)
-
@columns_hash = self.class.column_types.dup
-
-
init_internals
-
ensure_proper_type
-
populate_with_current_scope_attributes
-
-
assign_attributes(attributes) if attributes
-
-
yield self if block_given?
-
run_callbacks :initialize unless _initialize_callbacks.empty?
-
end
-
-
# Initialize an empty model object from +coder+. +coder+ must contain
-
# the attributes necessary for initializing an empty model object. For
-
# example:
-
#
-
# class Post < ActiveRecord::Base
-
# end
-
#
-
# post = Post.allocate
-
# post.init_with('attributes' => { 'title' => 'hello world' })
-
# post.title # => 'hello world'
-
1
def init_with(coder)
-
@attributes = self.class.initialize_attributes(coder['attributes'])
-
@columns_hash = self.class.column_types.merge(coder['column_types'] || {})
-
-
init_internals
-
-
@new_record = false
-
-
run_callbacks :find
-
run_callbacks :initialize
-
-
self
-
end
-
-
##
-
# :method: clone
-
# Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
-
# That means that modifying attributes of the clone will modify the original, since they will both point to the
-
# same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
-
#
-
# user = User.first
-
# new_user = user.clone
-
# user.name # => "Bob"
-
# new_user.name = "Joe"
-
# user.name # => "Joe"
-
#
-
# user.object_id == new_user.object_id # => false
-
# user.name.object_id == new_user.name.object_id # => true
-
#
-
# user.name.object_id == user.dup.name.object_id # => false
-
-
##
-
# :method: dup
-
# Duped objects have no id assigned and are treated as new records. Note
-
# that this is a "shallow" copy as it copies the object's attributes
-
# only, not its associations. The extent of a "deep" copy is application
-
# specific and is therefore left to the application to implement according
-
# to its need.
-
# The dup method does not preserve the timestamps (created|updated)_(at|on).
-
-
##
-
1
def initialize_dup(other) # :nodoc:
-
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
-
self.class.initialize_attributes(cloned_attributes, :serialized => false)
-
-
@attributes = cloned_attributes
-
@attributes[self.class.primary_key] = nil
-
-
run_callbacks(:initialize) unless _initialize_callbacks.empty?
-
-
@changed_attributes = {}
-
self.class.column_defaults.each do |attr, orig_value|
-
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
-
end
-
-
@aggregation_cache = {}
-
@association_cache = {}
-
@attributes_cache = {}
-
-
@new_record = true
-
-
ensure_proper_type
-
populate_with_current_scope_attributes
-
super
-
end
-
-
# Populate +coder+ with attributes about this record that should be
-
# serialized. The structure of +coder+ defined in this method is
-
# guaranteed to match the structure of +coder+ passed to the +init_with+
-
# method.
-
#
-
# Example:
-
#
-
# class Post < ActiveRecord::Base
-
# end
-
# coder = {}
-
# Post.new.encode_with(coder)
-
# coder # => {"attributes" => {"id" => nil, ... }}
-
1
def encode_with(coder)
-
coder['attributes'] = attributes
-
end
-
-
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
-
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
-
#
-
# Note that new records are different from any other record by definition, unless the
-
# other record is the receiver itself. Besides, if you fetch existing records with
-
# +select+ and leave the ID out, you're on your own, this predicate will return false.
-
#
-
# Note also that destroying a record preserves its ID in the model instance, so deleted
-
# models are still comparable.
-
1
def ==(comparison_object)
-
super ||
-
comparison_object.instance_of?(self.class) &&
-
id.present? &&
-
comparison_object.id == id
-
end
-
1
alias :eql? :==
-
-
# Delegates to id in order to allow two records of the same type and id to work with something like:
-
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
-
1
def hash
-
id.hash
-
end
-
-
# Freeze the attributes hash such that associations are still accessible, even on destroyed records.
-
1
def freeze
-
@attributes.freeze
-
self
-
end
-
-
# Returns +true+ if the attributes hash has been frozen.
-
1
def frozen?
-
@attributes.frozen?
-
end
-
-
# Allows sort on objects
-
1
def <=>(other_object)
-
if other_object.is_a?(self.class)
-
self.to_key <=> other_object.to_key
-
end
-
end
-
-
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
-
# attributes will be marked as read only since they cannot be saved.
-
1
def readonly?
-
@readonly
-
end
-
-
# Marks this record as read only.
-
1
def readonly!
-
@readonly = true
-
end
-
-
# Returns the connection currently associated with the class. This can
-
# also be used to "borrow" the connection to do database work that isn't
-
# easily done without going straight to SQL.
-
1
def connection
-
self.class.connection
-
end
-
-
# Returns the contents of the record as a nicely formatted string.
-
1
def inspect
-
inspection = if @attributes
-
self.class.column_names.collect { |name|
-
if has_attribute?(name)
-
"#{name}: #{attribute_for_inspect(name)}"
-
end
-
}.compact.join(", ")
-
else
-
"not initialized"
-
end
-
"#<#{self.class} #{inspection}>"
-
end
-
-
# Returns a hash of the given methods with their names as keys and returned values as values.
-
1
def slice(*methods)
-
Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access
-
end
-
-
1
private
-
-
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
-
# of the array, and then rescues from the possible NoMethodError. If those elements are
-
# ActiveRecord::Base's, then this triggers the various method_missing's that we have,
-
# which significantly impacts upon performance.
-
#
-
# So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
-
#
-
# See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
-
1
def to_ary # :nodoc:
-
nil
-
end
-
-
1
def init_internals
-
pk = self.class.primary_key
-
@attributes[pk] = nil unless @attributes.key?(pk)
-
-
@aggregation_cache = {}
-
@association_cache = {}
-
@attributes_cache = {}
-
@previously_changed = {}
-
@changed_attributes = {}
-
@readonly = false
-
@destroyed = false
-
@marked_for_destruction = false
-
@new_record = true
-
@txn = nil
-
@_start_transaction_state = {}
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Counter Cache
-
1
module CounterCache
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Resets one or more counter caches to their correct value using an SQL
-
# count query. This is useful when adding new counter caches, or if the
-
# counter has been corrupted or modified directly by SQL.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - The id of the object you wish to reset a counter on.
-
# * +counters+ - One or more counter names to reset
-
#
-
# ==== Examples
-
#
-
# # For Post with id #1 records reset the comments_count
-
# Post.reset_counters(1, :comments)
-
1
def reset_counters(id, *counters)
-
object = find(id)
-
counters.each do |association|
-
has_many_association = reflect_on_association(association.to_sym)
-
-
if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
-
has_many_association = has_many_association.through_reflection
-
end
-
-
foreign_key = has_many_association.foreign_key.to_s
-
child_class = has_many_association.klass
-
belongs_to = child_class.reflect_on_all_associations(:belongs_to)
-
reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
-
counter_name = reflection.counter_cache_column
-
-
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
-
arel_table[counter_name] => object.send(association).count
-
})
-
connection.update stmt
-
end
-
return true
-
end
-
-
# A generic "counter updater" implementation, intended primarily to be
-
# used by increment_counter and decrement_counter, but which may also
-
# be useful on its own. It simply does a direct SQL update for the record
-
# with the given ID, altering the given hash of counters by the amount
-
# given by the corresponding value:
-
#
-
# ==== Parameters
-
#
-
# * +id+ - The id of the object you wish to update a counter on or an Array of ids.
-
# * +counters+ - An Array of Hashes containing the names of the fields
-
# to update as keys and the amount to update the field by as values.
-
#
-
# ==== Examples
-
#
-
# # For the Post with id of 5, decrement the comment_count by 1, and
-
# # increment the action_count by 1
-
# Post.update_counters 5, :comment_count => -1, :action_count => 1
-
# # Executes the following SQL:
-
# # UPDATE posts
-
# # SET comment_count = COALESCE(comment_count, 0) - 1,
-
# # action_count = COALESCE(action_count, 0) + 1
-
# # WHERE id = 5
-
#
-
# # For the Posts with id of 10 and 15, increment the comment_count by 1
-
# Post.update_counters [10, 15], :comment_count => 1
-
# # Executes the following SQL:
-
# # UPDATE posts
-
# # SET comment_count = COALESCE(comment_count, 0) + 1
-
# # WHERE id IN (10, 15)
-
1
def update_counters(id, counters)
-
updates = counters.map do |counter_name, value|
-
operator = value < 0 ? '-' : '+'
-
quoted_column = connection.quote_column_name(counter_name)
-
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
-
end
-
-
where(primary_key => id).update_all updates.join(', ')
-
end
-
-
# Increment a number field by one, usually representing a count.
-
#
-
# This is used for caching aggregate values, so that they don't need to be computed every time.
-
# For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
-
# shown it would have to run an SQL query to find how many posts and comments there are.
-
#
-
# ==== Parameters
-
#
-
# * +counter_name+ - The name of the field that should be incremented.
-
# * +id+ - The id of the object that should be incremented.
-
#
-
# ==== Examples
-
#
-
# # Increment the post_count column for the record with an id of 5
-
# DiscussionBoard.increment_counter(:post_count, 5)
-
1
def increment_counter(counter_name, id)
-
update_counters(id, counter_name => 1)
-
end
-
-
# Decrement a number field by one, usually representing a count.
-
#
-
# This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
-
#
-
# ==== Parameters
-
#
-
# * +counter_name+ - The name of the field that should be decremented.
-
# * +id+ - The id of the object that should be decremented.
-
#
-
# ==== Examples
-
#
-
# # Decrement the post_count column for the record with an id of 5
-
# DiscussionBoard.decrement_counter(:post_count, 5)
-
1
def decrement_counter(counter_name, id)
-
update_counters(id, counter_name => -1)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module DynamicMatchers #:nodoc:
-
# This code in this file seems to have a lot of indirection, but the indirection
-
# is there to provide extension points for the activerecord-deprecated_finders
-
# gem. When we stop supporting activerecord-deprecated_finders (from Rails 5),
-
# then we can remove the indirection.
-
-
1
def respond_to?(name, include_private = false)
-
1
match = Method.match(self, name)
-
1
match && match.valid? || super
-
end
-
-
1
private
-
-
1
def method_missing(name, *arguments, &block)
-
match = Method.match(self, name)
-
-
if match && match.valid?
-
match.define
-
send(name, *arguments, &block)
-
else
-
super
-
end
-
end
-
-
1
class Method
-
1
@matchers = []
-
-
1
class << self
-
1
attr_reader :matchers
-
-
1
def match(model, name)
-
9
klass = matchers.find { |k| name =~ k.pattern }
-
1
klass.new(model, name) if klass
-
end
-
-
1
def pattern
-
8
/^#{prefix}_([_a-zA-Z]\w*)#{suffix}$/
-
end
-
-
1
def prefix
-
raise NotImplementedError
-
end
-
-
1
def suffix
-
6
''
-
end
-
end
-
-
1
attr_reader :model, :name, :attribute_names
-
-
1
def initialize(model, name)
-
@model = model
-
@name = name.to_s
-
@attribute_names = @name.match(self.class.pattern)[1].split('_and_')
-
@attribute_names.map! { |n| @model.attribute_aliases[n] || n }
-
end
-
-
1
def valid?
-
attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
-
end
-
-
1
def define
-
model.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def self.#{name}(#{signature})
-
#{body}
-
end
-
CODE
-
end
-
-
1
def body
-
raise NotImplementedError
-
end
-
end
-
-
1
module Finder
-
# Extended in activerecord-deprecated_finders
-
1
def body
-
result
-
end
-
-
# Extended in activerecord-deprecated_finders
-
1
def result
-
"#{finder}(#{attributes_hash})"
-
end
-
-
# Extended in activerecord-deprecated_finders
-
1
def signature
-
attribute_names.join(', ')
-
end
-
-
1
def attributes_hash
-
"{" + attribute_names.map { |name| ":#{name} => #{name}" }.join(',') + "}"
-
end
-
-
1
def finder
-
raise NotImplementedError
-
end
-
end
-
-
1
class FindBy < Method
-
1
Method.matchers << self
-
1
include Finder
-
-
1
def self.prefix
-
1
"find_by"
-
end
-
-
1
def finder
-
"find_by"
-
end
-
end
-
-
1
class FindByBang < Method
-
1
Method.matchers << self
-
1
include Finder
-
-
1
def self.prefix
-
1
"find_by"
-
end
-
-
1
def self.suffix
-
1
"!"
-
end
-
-
1
def finder
-
"find_by!"
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
-
# = Active Record Errors
-
#
-
# Generic Active Record exception class.
-
1
class ActiveRecordError < StandardError
-
end
-
-
# Raised when the single-table inheritance mechanism fails to locate the subclass
-
# (for example due to improper usage of column that +inheritance_column+ points to).
-
1
class SubclassNotFound < ActiveRecordError #:nodoc:
-
end
-
-
# Raised when an object assigned to an association has an incorrect type.
-
#
-
# class Ticket < ActiveRecord::Base
-
# has_many :patches
-
# end
-
#
-
# class Patch < ActiveRecord::Base
-
# belongs_to :ticket
-
# end
-
#
-
# # Comments are not patches, this assignment raises AssociationTypeMismatch.
-
# @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
-
1
class AssociationTypeMismatch < ActiveRecordError
-
end
-
-
# Raised when unserialized object's type mismatches one specified for serializable field.
-
1
class SerializationTypeMismatch < ActiveRecordError
-
end
-
-
# Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt>
-
# misses adapter field).
-
1
class AdapterNotSpecified < ActiveRecordError
-
end
-
-
# Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
-
1
class AdapterNotFound < ActiveRecordError
-
end
-
-
# Raised when connection to the database could not been established (for example when <tt>connection=</tt>
-
# is given a nil object).
-
1
class ConnectionNotEstablished < ActiveRecordError
-
end
-
-
# Raised when Active Record cannot find record by given id or set of ids.
-
1
class RecordNotFound < ActiveRecordError
-
end
-
-
# Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
-
# saved because record is invalid.
-
1
class RecordNotSaved < ActiveRecordError
-
end
-
-
# Raised by ActiveRecord::Base.destroy! when a call to destroy would return false.
-
1
class RecordNotDestroyed < ActiveRecordError
-
end
-
-
# Raised when SQL statement cannot be executed by the database (for example, it's often the case for
-
# MySQL when Ruby driver used is too old).
-
1
class StatementInvalid < ActiveRecordError
-
end
-
-
# Raised when SQL statement is invalid and the application gets a blank result.
-
1
class ThrowResult < ActiveRecordError
-
end
-
-
# Parent class for all specific exceptions which wrap database driver exceptions
-
# provides access to the original exception also.
-
1
class WrappedDatabaseException < StatementInvalid
-
1
attr_reader :original_exception
-
-
1
def initialize(message, original_exception)
-
super(message)
-
@original_exception = original_exception
-
end
-
end
-
-
# Raised when a record cannot be inserted because it would violate a uniqueness constraint.
-
1
class RecordNotUnique < WrappedDatabaseException
-
end
-
-
# Raised when a record cannot be inserted or updated because it references a non-existent record.
-
1
class InvalidForeignKey < WrappedDatabaseException
-
end
-
-
# Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example,
-
# when using +find+ method)
-
# does not match number of expected variables.
-
#
-
# For example, in
-
#
-
# Location.where("lat = ? AND lng = ?", 53.7362)
-
#
-
# two placeholders are given but only one variable to fill them.
-
1
class PreparedStatementInvalid < ActiveRecordError
-
end
-
-
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
-
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
-
# the page before the other.
-
#
-
# Read more about optimistic locking in ActiveRecord::Locking module RDoc.
-
1
class StaleObjectError < ActiveRecordError
-
1
attr_reader :record, :attempted_action
-
-
1
def initialize(record, attempted_action)
-
super("Attempted to #{attempted_action} a stale object: #{record.class.name}")
-
@record = record
-
@attempted_action = attempted_action
-
end
-
-
end
-
-
# Raised when association is being configured improperly or
-
# user tries to use offset and limit together with has_many or has_and_belongs_to_many associations.
-
1
class ConfigurationError < ActiveRecordError
-
end
-
-
# Raised on attempt to update record that is instantiated as read only.
-
1
class ReadOnlyRecord < ActiveRecordError
-
end
-
-
# ActiveRecord::Transactions::ClassMethods.transaction uses this exception
-
# to distinguish a deliberate rollback from other exceptional situations.
-
# Normally, raising an exception will cause the +transaction+ method to rollback
-
# the database transaction *and* pass on the exception. But if you raise an
-
# ActiveRecord::Rollback exception, then the database transaction will be rolled back,
-
# without passing on the exception.
-
#
-
# For example, you could do this in your controller to rollback a transaction:
-
#
-
# class BooksController < ActionController::Base
-
# def create
-
# Book.transaction do
-
# book = Book.new(params[:book])
-
# book.save!
-
# if today_is_friday?
-
# # The system must fail on Friday so that our support department
-
# # won't be out of job. We silently rollback this transaction
-
# # without telling the user.
-
# raise ActiveRecord::Rollback, "Call tech support!"
-
# end
-
# end
-
# # ActiveRecord::Rollback is the only exception that won't be passed on
-
# # by ActiveRecord::Base.transaction, so this line will still be reached
-
# # even on Friday.
-
# redirect_to root_url
-
# end
-
# end
-
1
class Rollback < ActiveRecordError
-
end
-
-
# Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
-
1
class DangerousAttributeError < ActiveRecordError
-
end
-
-
# Raised when unknown attributes are supplied via mass assignment.
-
1
class UnknownAttributeError < NoMethodError
-
end
-
-
# Raised when an error occurred while doing a mass assignment to an attribute through the
-
# <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
-
# offending attribute.
-
1
class AttributeAssignmentError < ActiveRecordError
-
1
attr_reader :exception, :attribute
-
1
def initialize(message, exception, attribute)
-
super(message)
-
@exception = exception
-
@attribute = attribute
-
end
-
end
-
-
# Raised when there are multiple errors while doing a mass assignment through the +attributes+
-
# method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
-
# objects, each corresponding to the error while assigning to an attribute.
-
1
class MultiparameterAssignmentErrors < ActiveRecordError
-
1
attr_reader :errors
-
1
def initialize(errors)
-
@errors = errors
-
end
-
end
-
-
# Raised when a primary key is needed, but there is not one specified in the schema or model.
-
1
class UnknownPrimaryKey < ActiveRecordError
-
1
attr_reader :model
-
-
1
def initialize(model)
-
super("Unknown primary key for table #{model.table_name} in model #{model}.")
-
@model = model
-
end
-
-
end
-
-
# Raised when a relation cannot be mutated because it's already loaded.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# relation = Task.all
-
# relation.loaded? # => true
-
#
-
# # Methods which try to mutate a loaded relation fail.
-
# relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation
-
# relation.limit!(5) # => ActiveRecord::ImmutableRelation
-
1
class ImmutableRelation < ActiveRecordError
-
end
-
-
1
class TransactionIsolationError < ActiveRecordError
-
end
-
end
-
1
require 'active_support/lazy_load_hooks'
-
-
1
module ActiveRecord
-
1
module Explain
-
1
def self.extended(base)
-
1
base.mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false
-
end
-
-
# If auto explain is enabled, this method triggers EXPLAIN logging for the
-
# queries triggered by the block if it takes more than the threshold as a
-
# whole. That is, the threshold is not checked against each individual
-
# query, but against the duration of the entire block. This approach is
-
# convenient for relations.
-
#
-
# The available_queries_for_explain thread variable collects the queries
-
# to be explained. If the value is nil, it means queries are not being
-
# currently collected. A false value indicates collecting is turned
-
# off. Otherwise it is an array of queries.
-
1
def logging_query_plan # :nodoc:
-
return yield unless logger
-
-
threshold = auto_explain_threshold_in_seconds
-
current = Thread.current
-
if threshold && current[:available_queries_for_explain].nil?
-
begin
-
queries = current[:available_queries_for_explain] = []
-
start = Time.now
-
result = yield
-
logger.warn(exec_explain(queries)) if Time.now - start > threshold
-
result
-
ensure
-
current[:available_queries_for_explain] = nil
-
end
-
else
-
yield
-
end
-
end
-
-
# Relation#explain needs to be able to collect the queries regardless of
-
# whether auto explain is enabled. This method serves that purpose.
-
1
def collecting_queries_for_explain # :nodoc:
-
current = Thread.current
-
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
-
return yield, current[:available_queries_for_explain]
-
ensure
-
# Note that the return value above does not depend on this assigment.
-
current[:available_queries_for_explain] = original
-
end
-
-
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
-
# Returns a formatted string ready to be logged.
-
1
def exec_explain(queries) # :nodoc:
-
str = queries && queries.map do |sql, bind|
-
[].tap do |msg|
-
msg << "EXPLAIN for: #{sql}"
-
unless bind.empty?
-
bind_msg = bind.map {|col, val| [col.name, val]}.inspect
-
msg.last << " #{bind_msg}"
-
end
-
msg << connection.explain(sql, bind)
-
end.join("\n")
-
end.join("\n")
-
-
# Overriding inspect to be more human readable, specially in the console.
-
def str.inspect
-
self
-
end
-
str
-
end
-
-
# Silences automatic EXPLAIN logging for the duration of the block.
-
#
-
# This has high priority, no EXPLAINs will be run even if downwards
-
# the threshold is set to 0.
-
#
-
# As the name of the method suggests this only applies to automatic
-
# EXPLAINs, manual calls to <tt>ActiveRecord::Relation#explain</tt> run.
-
1
def silence_auto_explain
-
current = Thread.current
-
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
-
yield
-
ensure
-
current[:available_queries_for_explain] = original
-
end
-
end
-
end
-
1
require 'active_support/notifications'
-
-
1
module ActiveRecord
-
1
class ExplainSubscriber # :nodoc:
-
1
def start(name, id, payload)
-
# unused
-
end
-
-
1
def finish(name, id, payload)
-
7
if queries = Thread.current[:available_queries_for_explain]
-
queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
-
end
-
end
-
-
# SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
-
# our own EXPLAINs now matter how loopingly beautiful that would be.
-
#
-
# On the other hand, we want to monitor the performance of our real database
-
# queries, not the performance of the access to the query cache.
-
1
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
-
1
EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)/i
-
1
def ignore_payload?(payload)
-
payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS
-
end
-
-
1
ActiveSupport::Notifications.subscribe("sql.active_record", new)
-
end
-
end
-
1
require 'erb'
-
1
require 'yaml'
-
-
1
module ActiveRecord
-
1
class FixtureSet
-
1
class File # :nodoc:
-
1
include Enumerable
-
-
##
-
# Open a fixture file named +file+. When called with a block, the block
-
# is called with the filehandle and the filehandle is automatically closed
-
# when the block finishes.
-
1
def self.open(file)
-
x = new file
-
block_given? ? yield(x) : x
-
end
-
-
1
def initialize(file)
-
@file = file
-
@rows = nil
-
end
-
-
1
def each(&block)
-
rows.each(&block)
-
end
-
-
1
RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ] # :nodoc:
-
-
1
private
-
1
def rows
-
return @rows if @rows
-
-
begin
-
data = YAML.load(render(IO.read(@file)))
-
rescue *RESCUE_ERRORS => error
-
raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
-
end
-
@rows = data ? validate(data).to_a : []
-
end
-
-
1
def render(content)
-
ERB.new(content).result
-
end
-
-
# Validate our unmarshalled data.
-
1
def validate(data)
-
unless Hash === data || YAML::Omap === data
-
raise Fixture::FormatError, 'fixture is not a hash'
-
end
-
-
raise Fixture::FormatError unless data.all? { |name, row| Hash === row }
-
data
-
end
-
end
-
end
-
end
-
1
require 'erb'
-
1
require 'yaml'
-
1
require 'zlib'
-
1
require 'active_support/dependencies'
-
1
require 'active_record/fixture_set/file'
-
1
require 'active_record/errors'
-
-
1
require 'active_support/deprecation' # temporary
-
-
1
module ActiveRecord
-
1
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
-
end
-
-
# \Fixtures are a way of organizing data that you want to test against; in short, sample data.
-
#
-
# They are stored in YAML files, one file per model, which are placed in the directory
-
# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
-
# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
-
# The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
-
# <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a fixture file looks
-
# like this:
-
#
-
# rubyonrails:
-
# id: 1
-
# name: Ruby on Rails
-
# url: http://www.rubyonrails.org
-
#
-
# google:
-
# id: 2
-
# name: Google
-
# url: http://www.google.com
-
#
-
# This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and
-
# is followed by an indented list of key/value pairs in the "key: value" format. Records are
-
# separated by a blank line for your viewing pleasure.
-
#
-
# Note that fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
-
# See http://yaml.org/type/omap.html
-
# for the specification. You will need ordered fixtures when you have foreign key constraints
-
# on keys in the same table. This is commonly needed for tree structures. Example:
-
#
-
# --- !omap
-
# - parent:
-
# id: 1
-
# parent_id: NULL
-
# title: Parent
-
# - child:
-
# id: 2
-
# parent_id: 1
-
# title: Child
-
#
-
# = Using Fixtures in Test Cases
-
#
-
# Since fixtures are a testing construct, we use them in our unit and functional tests. There
-
# are two ways to use the fixtures, but first let's take a look at a sample unit test:
-
#
-
# require 'test_helper'
-
#
-
# class WebSiteTest < ActiveSupport::TestCase
-
# test "web_site_count" do
-
# assert_equal 2, WebSite.count
-
# end
-
# end
-
#
-
# By default, <tt>test_helper.rb</tt> will load all of your fixtures into your test database,
-
# so this test will succeed.
-
#
-
# The testing environment will automatically load the all fixtures into the database before each
-
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
-
#
-
# In addition to being available in the database, the fixture's data may also be accessed by
-
# using a special dynamic method, which has the same name as the model, and accepts the
-
# name of the fixture to instantiate:
-
#
-
# test "find" do
-
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
-
# end
-
#
-
# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
-
# following tests:
-
#
-
# test "find_alt_method_1" do
-
# assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
-
# end
-
#
-
# test "find_alt_method_2" do
-
# assert_equal "Ruby on Rails", @rubyonrails.name
-
# end
-
#
-
# In order to use these methods to access fixtured data within your testcases, you must specify one of the
-
# following in your <tt>ActiveSupport::TestCase</tt>-derived class:
-
#
-
# - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
-
# self.use_instantiated_fixtures = true
-
#
-
# - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
-
# self.use_instantiated_fixtures = :no_instances
-
#
-
# Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
-
# traversed in the database to create the fixture hash and/or instance variables. This is expensive for
-
# large sets of fixtured data.
-
#
-
# = Dynamic fixtures with ERB
-
#
-
# Some times you don't care about the content of the fixtures as much as you care about the volume.
-
# In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
-
# testing, like:
-
#
-
# <% 1.upto(1000) do |i| %>
-
# fix_<%= i %>:
-
# id: <%= i %>
-
# name: guy_<%= 1 %>
-
# <% end %>
-
#
-
# This will create 1000 very simple fixtures.
-
#
-
# Using ERB, you can also inject dynamic values into your fixtures with inserts like
-
# <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
-
# This is however a feature to be used with some caution. The point of fixtures are that they're
-
# stable units of predictable sample data. If you feel that you need to inject dynamic values, then
-
# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
-
# in fixtures are to be considered a code smell.
-
#
-
# = Transactional Fixtures
-
#
-
# Test cases can use begin+rollback to isolate their changes to the database instead of having to
-
# delete+insert for every test case.
-
#
-
# class FooTest < ActiveSupport::TestCase
-
# self.use_transactional_fixtures = true
-
#
-
# test "godzilla" do
-
# assert !Foo.all.empty?
-
# Foo.destroy_all
-
# assert Foo.all.empty?
-
# end
-
#
-
# test "godzilla aftermath" do
-
# assert !Foo.all.empty?
-
# end
-
# end
-
#
-
# If you preload your test database with all fixture data (probably in the rake task) and use
-
# transactional fixtures, then you may omit all fixtures declarations in your test cases since
-
# all the data's already there and every case rolls back its changes.
-
#
-
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
-
# true. This will provide access to fixture data for every table that has been loaded through
-
# fixtures (depending on the value of +use_instantiated_fixtures+).
-
#
-
# When *not* to use transactional fixtures:
-
#
-
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
-
# all parent transactions commit, particularly, the fixtures transaction which is begun in setup
-
# and rolled back in teardown. Thus, you won't be able to verify
-
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
-
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
-
# Use InnoDB, MaxDB, or NDB instead.
-
#
-
# = Advanced Fixtures
-
#
-
# Fixtures that don't specify an ID get some extra features:
-
#
-
# * Stable, autogenerated IDs
-
# * Label references for associations (belongs_to, has_one, has_many)
-
# * HABTM associations as inline lists
-
# * Autofilled timestamp columns
-
# * Fixture label interpolation
-
# * Support for YAML defaults
-
#
-
# == Stable, Autogenerated IDs
-
#
-
# Here, have a monkey fixture:
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
#
-
# reginald:
-
# id: 2
-
# name: Reginald the Pirate
-
#
-
# Each of these fixtures has two unique identifiers: one for the database
-
# and one for the humans. Why don't we generate the primary key instead?
-
# Hashing each fixture's label yields a consistent ID:
-
#
-
# george: # generated id: 503576764
-
# name: George the Monkey
-
#
-
# reginald: # generated id: 324201669
-
# name: Reginald the Pirate
-
#
-
# Active Record looks at the fixture's model class, discovers the correct
-
# primary key, and generates it right before inserting the fixture
-
# into the database.
-
#
-
# The generated ID for a given label is constant, so we can discover
-
# any fixture's ID without loading anything, as long as we know the label.
-
#
-
# == Label references for associations (belongs_to, has_one, has_many)
-
#
-
# Specifying foreign keys in fixtures can be very fragile, not to
-
# mention difficult to read. Since Active Record can figure out the ID of
-
# any fixture from its label, you can specify FK's by label instead of ID.
-
#
-
# === belongs_to
-
#
-
# Let's break out some more monkeys and pirates.
-
#
-
# ### in pirates.yml
-
#
-
# reginald:
-
# id: 1
-
# name: Reginald the Pirate
-
# monkey_id: 1
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
# pirate_id: 1
-
#
-
# Add a few more monkeys and pirates and break this into multiple files,
-
# and it gets pretty hard to keep track of what's going on. Let's
-
# use labels instead of IDs:
-
#
-
# ### in pirates.yml
-
#
-
# reginald:
-
# name: Reginald the Pirate
-
# monkey: george
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# name: George the Monkey
-
# pirate: reginald
-
#
-
# Pow! All is made clear. Active Record reflects on the fixture's model class,
-
# finds all the +belongs_to+ associations, and allows you to specify
-
# a target *label* for the *association* (monkey: george) rather than
-
# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
-
#
-
# ==== Polymorphic belongs_to
-
#
-
# Supporting polymorphic relationships is a little bit more complicated, since
-
# Active Record needs to know what type your association is pointing at. Something
-
# like this should look familiar:
-
#
-
# ### in fruit.rb
-
#
-
# belongs_to :eater, :polymorphic => true
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# id: 1
-
# name: apple
-
# eater_id: 1
-
# eater_type: Monkey
-
#
-
# Can we do better? You bet!
-
#
-
# apple:
-
# eater: george (Monkey)
-
#
-
# Just provide the polymorphic target type and Active Record will take care of the rest.
-
#
-
# === has_and_belongs_to_many
-
#
-
# Time to give our monkey some fruit.
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# id: 1
-
# name: apple
-
#
-
# orange:
-
# id: 2
-
# name: orange
-
#
-
# grape:
-
# id: 3
-
# name: grape
-
#
-
# ### in fruits_monkeys.yml
-
#
-
# apple_george:
-
# fruit_id: 1
-
# monkey_id: 1
-
#
-
# orange_george:
-
# fruit_id: 2
-
# monkey_id: 1
-
#
-
# grape_george:
-
# fruit_id: 3
-
# monkey_id: 1
-
#
-
# Let's make the HABTM fixture go away.
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
# fruits: apple, orange, grape
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# name: apple
-
#
-
# orange:
-
# name: orange
-
#
-
# grape:
-
# name: grape
-
#
-
# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
-
# on George's fixture, but we could've just as easily specified a list
-
# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
-
# the fixture's model class and discovers the +has_and_belongs_to_many+
-
# associations.
-
#
-
# == Autofilled Timestamp Columns
-
#
-
# If your table/model specifies any of Active Record's
-
# standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
-
# they will automatically be set to <tt>Time.now</tt>.
-
#
-
# If you've set specific values, they'll be left alone.
-
#
-
# == Fixture label interpolation
-
#
-
# The label of the current fixture is always available as a column value:
-
#
-
# geeksomnia:
-
# name: Geeksomnia's Account
-
# subdomain: $LABEL
-
#
-
# Also, sometimes (like when porting older join table fixtures) you'll need
-
# to be able to get a hold of the identifier for a given label. ERB
-
# to the rescue:
-
#
-
# george_reginald:
-
# monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %>
-
# pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %>
-
#
-
# == Support for YAML defaults
-
#
-
# You probably already know how to use YAML to set and reuse defaults in
-
# your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
-
#
-
# DEFAULTS: &DEFAULTS
-
# created_on: <%= 3.weeks.ago.to_s(:db) %>
-
#
-
# first:
-
# name: Smurf
-
# *DEFAULTS
-
#
-
# second:
-
# name: Fraggle
-
# *DEFAULTS
-
#
-
# Any fixture labeled "DEFAULTS" is safely ignored.
-
1
class FixtureSet
-
#--
-
# An instance of FixtureSet is normally stored in a single YAML file and possibly in a folder with the same name.
-
#++
-
-
1
MAX_ID = 2 ** 30 - 1
-
-
1
@@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
-
-
1
def self.default_fixture_model_name(fixture_set_name) # :nodoc:
-
ActiveRecord::Base.pluralize_table_names ?
-
fixture_set_name.singularize.camelize :
-
fixture_set_name.camelize
-
end
-
-
1
def self.default_fixture_table_name(fixture_set_name) # :nodoc:
-
"#{ ActiveRecord::Base.table_name_prefix }"\
-
"#{ fixture_set_name.tr('/', '_') }"\
-
"#{ ActiveRecord::Base.table_name_suffix }".to_sym
-
end
-
-
1
def self.reset_cache
-
@@all_cached_fixtures.clear
-
end
-
-
1
def self.cache_for_connection(connection)
-
@@all_cached_fixtures[connection]
-
end
-
-
1
def self.fixture_is_cached?(connection, table_name)
-
cache_for_connection(connection)[table_name]
-
end
-
-
1
def self.cached_fixtures(connection, keys_to_fetch = nil)
-
if keys_to_fetch
-
cache_for_connection(connection).values_at(*keys_to_fetch)
-
else
-
cache_for_connection(connection).values
-
end
-
end
-
-
1
def self.cache_fixtures(connection, fixtures_map)
-
cache_for_connection(connection).update(fixtures_map)
-
end
-
-
1
def self.instantiate_fixtures(object, fixture_set, load_instances = true)
-
if load_instances
-
fixture_set.each do |fixture_name, fixture|
-
begin
-
object.instance_variable_set "@#{fixture_name}", fixture.find
-
rescue FixtureClassNotFound
-
nil
-
end
-
end
-
end
-
end
-
-
1
def self.instantiate_all_loaded_fixtures(object, load_instances = true)
-
all_loaded_fixtures.each_value do |fixture_set|
-
instantiate_fixtures(object, fixture_set, load_instances)
-
end
-
end
-
-
1
cattr_accessor :all_loaded_fixtures
-
1
self.all_loaded_fixtures = {}
-
-
1
def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {})
-
fixture_set_names = Array(fixture_set_names).map(&:to_s)
-
class_names = class_names.stringify_keys
-
-
# FIXME: Apparently JK uses this.
-
connection = block_given? ? yield : ActiveRecord::Base.connection
-
-
files_to_read = fixture_set_names.reject { |fs_name|
-
fixture_is_cached?(connection, fs_name)
-
}
-
-
unless files_to_read.empty?
-
connection.disable_referential_integrity do
-
fixtures_map = {}
-
-
fixture_sets = files_to_read.map do |fs_name|
-
fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
-
connection,
-
fs_name,
-
class_names[fs_name] || default_fixture_model_name(fs_name),
-
::File.join(fixtures_directory, fs_name))
-
end
-
-
all_loaded_fixtures.update(fixtures_map)
-
-
connection.transaction(:requires_new => true) do
-
fixture_sets.each do |fs|
-
conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
-
table_rows = fs.table_rows
-
-
table_rows.keys.each do |table|
-
conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
-
end
-
-
table_rows.each do |fixture_set_name, rows|
-
rows.each do |row|
-
conn.insert_fixture(row, fixture_set_name)
-
end
-
end
-
end
-
-
# Cap primary key sequences to max(pk).
-
if connection.respond_to?(:reset_pk_sequence!)
-
fixture_sets.each do |fs|
-
connection.reset_pk_sequence!(fs.table_name)
-
end
-
end
-
end
-
-
cache_fixtures(connection, fixtures_map)
-
end
-
end
-
cached_fixtures(connection, fixture_set_names)
-
end
-
-
# Returns a consistent, platform-independent identifier for +label+.
-
# Identifiers are positive integers less than 2^32.
-
1
def self.identify(label)
-
Zlib.crc32(label.to_s) % MAX_ID
-
end
-
-
1
attr_reader :table_name, :name, :fixtures, :model_class
-
-
1
def initialize(connection, name, class_name, path)
-
@fixtures = {} # Ordered hash
-
@name = name
-
@path = path
-
-
if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
-
@model_class = class_name
-
else
-
@model_class = class_name.constantize rescue nil
-
end
-
-
@connection = ( model_class.respond_to?(:connection) ?
-
model_class.connection : connection )
-
-
@table_name = ( model_class.respond_to?(:table_name) ?
-
model_class.table_name :
-
self.class.default_fixture_table_name(name) )
-
-
read_fixture_files
-
end
-
-
1
def [](x)
-
fixtures[x]
-
end
-
-
1
def []=(k,v)
-
fixtures[k] = v
-
end
-
-
1
def each(&block)
-
fixtures.each(&block)
-
end
-
-
1
def size
-
fixtures.size
-
end
-
-
# Return a hash of rows to be inserted. The key is the table, the value is
-
# a list of rows to insert to that table.
-
1
def table_rows
-
now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
-
now = now.to_s(:db)
-
-
# allow a standard key to be used for doing defaults in YAML
-
fixtures.delete('DEFAULTS')
-
-
# track any join tables we need to insert later
-
rows = Hash.new { |h,table| h[table] = [] }
-
-
rows[table_name] = fixtures.map do |label, fixture|
-
row = fixture.to_hash
-
-
if model_class && model_class < ActiveRecord::Base
-
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
-
if model_class.record_timestamps
-
timestamp_column_names.each do |c_name|
-
row[c_name] = now unless row.key?(c_name)
-
end
-
end
-
-
# interpolate the fixture label
-
row.each do |key, value|
-
row[key] = label if value == "$LABEL"
-
end
-
-
# generate a primary key if necessary
-
if has_primary_key_column? && !row.include?(primary_key_name)
-
row[primary_key_name] = ActiveRecord::FixtureSet.identify(label)
-
end
-
-
# If STI is used, find the correct subclass for association reflection
-
reflection_class =
-
if row.include?(inheritance_column_name)
-
row[inheritance_column_name].constantize rescue model_class
-
else
-
model_class
-
end
-
-
reflection_class.reflect_on_all_associations.each do |association|
-
case association.macro
-
when :belongs_to
-
# Do not replace association name with association foreign key if they are named the same
-
fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
-
-
if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
-
if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
-
# support polymorphic belongs_to as "label (Type)"
-
row[association.foreign_type] = $1
-
end
-
-
row[fk_name] = ActiveRecord::FixtureSet.identify(value)
-
end
-
when :has_and_belongs_to_many
-
if (targets = row.delete(association.name.to_s))
-
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
-
table_name = association.join_table
-
rows[table_name].concat targets.map { |target|
-
{ association.foreign_key => row[primary_key_name],
-
association.association_foreign_key => ActiveRecord::FixtureSet.identify(target) }
-
}
-
end
-
end
-
end
-
end
-
-
row
-
end
-
rows
-
end
-
-
1
private
-
1
def primary_key_name
-
@primary_key_name ||= model_class && model_class.primary_key
-
end
-
-
1
def has_primary_key_column?
-
@has_primary_key_column ||= primary_key_name &&
-
model_class.columns.any? { |c| c.name == primary_key_name }
-
end
-
-
1
def timestamp_column_names
-
@timestamp_column_names ||=
-
%w(created_at created_on updated_at updated_on) & column_names
-
end
-
-
1
def inheritance_column_name
-
@inheritance_column_name ||= model_class && model_class.inheritance_column
-
end
-
-
1
def column_names
-
@column_names ||= @connection.columns(@table_name).collect { |c| c.name }
-
end
-
-
1
def read_fixture_files
-
yaml_files = Dir["#{@path}/**/*.yml"].select { |f|
-
::File.file?(f)
-
} + [yaml_file_path]
-
-
yaml_files.each do |file|
-
FixtureSet::File.open(file) do |fh|
-
fh.each do |fixture_name, row|
-
fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
-
end
-
end
-
end
-
end
-
-
1
def yaml_file_path
-
"#{@path}.yml"
-
end
-
-
end
-
-
#--
-
# Deprecate 'Fixtures' in favor of 'FixtureSet'.
-
#++
-
# :nodoc:
-
1
Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', 'ActiveRecord::FixtureSet')
-
-
1
class Fixture #:nodoc:
-
1
include Enumerable
-
-
1
class FixtureError < StandardError #:nodoc:
-
end
-
-
1
class FormatError < FixtureError #:nodoc:
-
end
-
-
1
attr_reader :model_class, :fixture
-
-
1
def initialize(fixture, model_class)
-
@fixture = fixture
-
@model_class = model_class
-
end
-
-
1
def class_name
-
model_class.name if model_class
-
end
-
-
1
def each
-
fixture.each { |item| yield item }
-
end
-
-
1
def [](key)
-
fixture[key]
-
end
-
-
1
alias :to_hash :fixture
-
-
1
def find
-
if model_class
-
model_class.find(fixture[model_class.primary_key])
-
else
-
raise FixtureClassNotFound, "No class attached to find."
-
end
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module TestFixtures
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
setup :setup_fixtures
-
1
teardown :teardown_fixtures
-
-
1
class_attribute :fixture_path
-
1
class_attribute :fixture_table_names
-
1
class_attribute :fixture_class_names
-
1
class_attribute :use_transactional_fixtures
-
1
class_attribute :use_instantiated_fixtures # true, false, or :no_instances
-
1
class_attribute :pre_loaded_fixtures
-
-
1
self.fixture_table_names = []
-
1
self.use_transactional_fixtures = true
-
1
self.use_instantiated_fixtures = false
-
1
self.pre_loaded_fixtures = false
-
-
1
self.fixture_class_names = Hash.new do |h, fixture_set_name|
-
h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name)
-
end
-
end
-
-
1
module ClassMethods
-
# Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
-
#
-
# Examples:
-
#
-
# set_fixture_class :some_fixture => SomeModel,
-
# 'namespaced/fixture' => Another::Model
-
#
-
# The keys must be the fixture names, that coincide with the short paths to the fixture files.
-
#--
-
# It is also possible to pass the class name instead of the class:
-
# set_fixture_class 'some_fixture' => 'SomeModel'
-
# I think this option is redundant, i propose to deprecate it.
-
# Isn't it easier to always pass the class itself?
-
# (2011-12-20 alexeymuranov)
-
#++
-
1
def set_fixture_class(class_names = {})
-
self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys)
-
end
-
-
1
def fixtures(*fixture_set_names)
-
if fixture_set_names.first == :all
-
fixture_set_names = Dir["#{fixture_path}/**/*.yml"].map { |f|
-
File.basename f, '.yml'
-
}
-
else
-
fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
-
end
-
-
self.fixture_table_names |= fixture_set_names
-
require_fixture_classes(fixture_set_names)
-
setup_fixture_accessors(fixture_set_names)
-
end
-
-
1
def try_to_load_dependency(file_name)
-
require_dependency file_name
-
rescue LoadError => e
-
# Let's hope the developer has included it himself
-
-
# Let's warn in case this is a subdependency, otherwise
-
# subdependency error messages are totally cryptic
-
if ActiveRecord::Base.logger
-
ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
-
end
-
end
-
-
1
def require_fixture_classes(fixture_set_names = nil)
-
if fixture_set_names
-
fixture_set_names = fixture_set_names.map { |n| n.to_s }
-
else
-
fixture_set_names = fixture_table_names
-
end
-
-
fixture_set_names.each do |file_name|
-
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
-
try_to_load_dependency(file_name)
-
end
-
end
-
-
1
def setup_fixture_accessors(fixture_set_names = nil)
-
fixture_set_names = Array(fixture_set_names || fixture_table_names)
-
methods = Module.new do
-
fixture_set_names.each do |fs_name|
-
fs_name = fs_name.to_s
-
accessor_name = fs_name.tr('/', '_').to_sym
-
-
define_method(accessor_name) do |*fixture_names|
-
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
-
-
@fixture_cache[fs_name] ||= {}
-
-
instances = fixture_names.map do |f_name|
-
f_name = f_name.to_s
-
@fixture_cache[fs_name].delete(f_name) if force_reload
-
-
if @loaded_fixtures[fs_name][f_name]
-
@fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
-
else
-
raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
-
end
-
end
-
-
instances.size == 1 ? instances.first : instances
-
end
-
private accessor_name
-
end
-
end
-
include methods
-
end
-
-
1
def uses_transaction(*methods)
-
@uses_transaction = [] unless defined?(@uses_transaction)
-
@uses_transaction.concat methods.map { |m| m.to_s }
-
end
-
-
1
def uses_transaction?(method)
-
@uses_transaction = [] unless defined?(@uses_transaction)
-
@uses_transaction.include?(method.to_s)
-
end
-
end
-
-
1
def run_in_transaction?
-
use_transactional_fixtures &&
-
!self.class.uses_transaction?(method_name)
-
end
-
-
1
def setup_fixtures
-
return if ActiveRecord::Base.configurations.blank?
-
-
if pre_loaded_fixtures && !use_transactional_fixtures
-
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
-
end
-
-
@fixture_cache = {}
-
@fixture_connections = []
-
@@already_loaded_fixtures ||= {}
-
-
# Load fixtures once and begin transaction.
-
if run_in_transaction?
-
if @@already_loaded_fixtures[self.class]
-
@loaded_fixtures = @@already_loaded_fixtures[self.class]
-
else
-
@loaded_fixtures = load_fixtures
-
@@already_loaded_fixtures[self.class] = @loaded_fixtures
-
end
-
@fixture_connections = enlist_fixture_connections
-
@fixture_connections.each do |connection|
-
connection.begin_transaction joinable: false
-
end
-
# Load fixtures for every test.
-
else
-
ActiveRecord::FixtureSet.reset_cache
-
@@already_loaded_fixtures[self.class] = nil
-
@loaded_fixtures = load_fixtures
-
end
-
-
# Instantiate fixtures for every test if requested.
-
instantiate_fixtures if use_instantiated_fixtures
-
end
-
-
1
def teardown_fixtures
-
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
-
-
unless run_in_transaction?
-
ActiveRecord::FixtureSet.reset_cache
-
end
-
-
# Rollback changes if a transaction is active.
-
if run_in_transaction?
-
@fixture_connections.each do |connection|
-
connection.rollback_transaction if connection.transaction_open?
-
end
-
@fixture_connections.clear
-
end
-
ActiveRecord::Base.clear_active_connections!
-
end
-
-
1
def enlist_fixture_connections
-
ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
-
end
-
-
1
private
-
1
def load_fixtures
-
fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
-
Hash[fixtures.map { |f| [f.name, f] }]
-
end
-
-
# for pre_loaded_fixtures, only require the classes once. huge speed improvement
-
1
@@required_fixture_classes = false
-
-
1
def instantiate_fixtures
-
if pre_loaded_fixtures
-
raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
-
unless @@required_fixture_classes
-
self.class.require_fixture_classes ActiveRecord::FixtureSet.all_loaded_fixtures.keys
-
@@required_fixture_classes = true
-
end
-
ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
-
else
-
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
-
@loaded_fixtures.each_value do |fixture_set|
-
ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
-
end
-
end
-
end
-
-
1
def load_instances?
-
use_instantiated_fixtures != :no_instances
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Inheritance
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
# Determine whether to store the full constant name including namespace when using STI
-
1
class_attribute :store_full_sti_class, instance_writer: false
-
1
self.store_full_sti_class = true
-
end
-
-
1
module ClassMethods
-
# True if this isn't a concrete subclass needing a STI type condition.
-
1
def descends_from_active_record?
-
if self == Base
-
false
-
elsif superclass.abstract_class?
-
superclass.descends_from_active_record?
-
else
-
superclass == Base || !columns_hash.include?(inheritance_column)
-
end
-
end
-
-
1
def finder_needs_type_condition? #:nodoc:
-
# This is like this because benchmarking justifies the strange :false stuff
-
:true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
-
end
-
-
1
def symbolized_base_class
-
@symbolized_base_class ||= base_class.to_s.to_sym
-
end
-
-
1
def symbolized_sti_name
-
@symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
-
end
-
-
# Returns the class descending directly from ActiveRecord::Base, or
-
# an abstract class, if any, in the inheritance hierarchy.
-
#
-
# If A extends AR::Base, A.base_class will return A. If B descends from A
-
# through some arbitrarily deep hierarchy, B.base_class will return A.
-
#
-
# If B < A and C < B and if A is an abstract_class then both B.base_class
-
# and C.base_class would return B as the answer since A is an abstract_class.
-
1
def base_class
-
unless self < Base
-
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
-
end
-
-
if superclass == Base || superclass.abstract_class?
-
self
-
else
-
superclass.base_class
-
end
-
end
-
-
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
-
# If you are using inheritance with ActiveRecord and don't want child classes
-
# to utilize the implied STI table name of the parent class, this will need to be true.
-
# For example, given the following:
-
#
-
# class SuperClass < ActiveRecord::Base
-
# self.abstract_class = true
-
# end
-
# class Child < SuperClass
-
# self.table_name = 'the_table_i_really_want'
-
# end
-
#
-
#
-
# <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt>
-
#
-
1
attr_accessor :abstract_class
-
-
# Returns whether this class is an abstract class or not.
-
1
def abstract_class?
-
defined?(@abstract_class) && @abstract_class == true
-
end
-
-
1
def sti_name
-
store_full_sti_class ? name : name.demodulize
-
end
-
-
# Finder methods must instantiate through this method to work with the
-
# single-table inheritance model that makes it possible to create
-
# objects of different types from the same table.
-
1
def instantiate(record, column_types = {})
-
sti_class = find_sti_class(record[inheritance_column])
-
column_types = sti_class.decorate_columns(column_types)
-
sti_class.allocate.init_with('attributes' => record, 'column_types' => column_types)
-
end
-
-
1
protected
-
-
# Returns the class type of the record using the current module as a prefix. So descendants of
-
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
-
1
def compute_type(type_name)
-
if type_name.match(/^::/)
-
# If the type is prefixed with a scope operator then we assume that
-
# the type_name is an absolute reference.
-
ActiveSupport::Dependencies.constantize(type_name)
-
else
-
# Build a list of candidates to search for
-
candidates = []
-
name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
-
candidates << type_name
-
-
candidates.each do |candidate|
-
begin
-
constant = ActiveSupport::Dependencies.constantize(candidate)
-
return constant if candidate == constant.to_s
-
rescue NameError => e
-
# We don't want to swallow NoMethodError < NameError errors
-
raise e unless e.instance_of?(NameError)
-
end
-
end
-
-
raise NameError, "uninitialized constant #{candidates.first}"
-
end
-
end
-
-
1
private
-
-
1
def find_sti_class(type_name)
-
if type_name.blank? || !columns_hash.include?(inheritance_column)
-
self
-
else
-
begin
-
if store_full_sti_class
-
ActiveSupport::Dependencies.constantize(type_name)
-
else
-
compute_type(type_name)
-
end
-
rescue NameError
-
raise SubclassNotFound,
-
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
-
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
-
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
-
"or overwrite #{name}.inheritance_column to use another column for that information."
-
end
-
end
-
end
-
-
1
def type_condition(table = arel_table)
-
sti_column = table[inheritance_column.to_sym]
-
sti_names = ([self] + descendants).map { |model| model.sti_name }
-
-
sti_column.in(sti_names)
-
end
-
end
-
-
1
private
-
-
# Sets the attribute used for single table inheritance to this class name if this is not the
-
# ActiveRecord::Base descendant.
-
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
-
# do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
-
# No such attribute would be set for objects of the Message class in that example.
-
1
def ensure_proper_type
-
klass = self.class
-
if klass.finder_needs_type_condition?
-
write_attribute(klass.inheritance_column, klass.sti_name)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Integration
-
# Returns a String, which Action Pack uses for constructing an URL to this
-
# object. The default implementation returns this record's id as a String,
-
# or nil if this record's unsaved.
-
#
-
# For example, suppose that you have a User model, and that you have a
-
# <tt>resources :users</tt> route. Normally, +user_path+ will
-
# construct a path with the user object's 'id' in it:
-
#
-
# user = User.find_by_name('Phusion')
-
# user_path(user) # => "/users/1"
-
#
-
# You can override +to_param+ in your model to make +user_path+ construct
-
# a path using the user's name instead of the user's id:
-
#
-
# class User < ActiveRecord::Base
-
# def to_param # overridden
-
# name
-
# end
-
# end
-
#
-
# user = User.find_by_name('Phusion')
-
# user_path(user) # => "/users/Phusion"
-
1
def to_param
-
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
-
id && id.to_s # Be sure to stringify the id for routes
-
end
-
-
# Returns a cache key that can be used to identify this record.
-
#
-
# ==== Examples
-
#
-
# Product.new.cache_key # => "products/new"
-
# Product.find(5).cache_key # => "products/5" (updated_at not available)
-
# Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
-
1
def cache_key
-
case
-
when new_record?
-
"#{self.class.model_name.cache_key}/new"
-
when timestamp = self[:updated_at]
-
timestamp = timestamp.utc.to_s(:nsec)
-
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
-
else
-
"#{self.class.model_name.cache_key}/#{id}"
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Locking
-
# == What is Optimistic Locking
-
#
-
# Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
-
# conflicts with the data. It does this by checking whether another process has made changes to a record since
-
# it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
-
# and the update is ignored.
-
#
-
# Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
-
#
-
# == Usage
-
#
-
# Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
-
# record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
-
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
-
#
-
# p1 = Person.find(1)
-
# p2 = Person.find(1)
-
#
-
# p1.first_name = "Michael"
-
# p1.save
-
#
-
# p2.first_name = "should fail"
-
# p2.save # Raises a ActiveRecord::StaleObjectError
-
#
-
# Optimistic locking will also check for stale data when objects are destroyed. Example:
-
#
-
# p1 = Person.find(1)
-
# p2 = Person.find(1)
-
#
-
# p1.first_name = "Michael"
-
# p1.save
-
#
-
# p2.destroy # Raises a ActiveRecord::StaleObjectError
-
#
-
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
-
# or otherwise apply the business logic needed to resolve the conflict.
-
#
-
# This locking mechanism will function inside a single Ruby process. To make it work across all
-
# web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
-
#
-
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
-
# To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
-
#
-
# class Person < ActiveRecord::Base
-
# self.locking_column = :lock_person
-
# end
-
#
-
1
module Optimistic
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :lock_optimistically, instance_writer: false
-
1
self.lock_optimistically = true
-
end
-
-
1
def locking_enabled? #:nodoc:
-
self.class.locking_enabled?
-
end
-
-
1
private
-
1
def increment_lock
-
lock_col = self.class.locking_column
-
previous_lock_value = send(lock_col).to_i
-
send(lock_col + '=', previous_lock_value + 1)
-
end
-
-
1
def update(attribute_names = @attributes.keys) #:nodoc:
-
return super unless locking_enabled?
-
return 0 if attribute_names.empty?
-
-
lock_col = self.class.locking_column
-
previous_lock_value = send(lock_col).to_i
-
increment_lock
-
-
attribute_names += [lock_col]
-
attribute_names.uniq!
-
-
begin
-
relation = self.class.unscoped
-
-
stmt = relation.where(
-
relation.table[self.class.primary_key].eq(id).and(
-
relation.table[lock_col].eq(self.class.quote_value(previous_lock_value))
-
)
-
).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
-
-
affected_rows = connection.update stmt
-
-
unless affected_rows == 1
-
raise ActiveRecord::StaleObjectError.new(self, "update")
-
end
-
-
affected_rows
-
-
# If something went wrong, revert the version.
-
rescue Exception
-
send(lock_col + '=', previous_lock_value)
-
raise
-
end
-
end
-
-
1
def destroy_row
-
affected_rows = super
-
-
if locking_enabled? && affected_rows != 1
-
raise ActiveRecord::StaleObjectError.new(self, "destroy")
-
end
-
-
affected_rows
-
end
-
-
1
def relation_for_destroy
-
relation = super
-
-
if locking_enabled?
-
column_name = self.class.locking_column
-
column = self.class.columns_hash[column_name]
-
substitute = connection.substitute_at(column, relation.bind_values.length)
-
-
relation = relation.where(self.class.arel_table[column_name].eq(substitute))
-
relation.bind_values << [column, self[column_name].to_i]
-
end
-
-
relation
-
end
-
-
1
module ClassMethods
-
1
DEFAULT_LOCKING_COLUMN = 'lock_version'
-
-
# Returns true if the +lock_optimistically+ flag is set to true
-
# (which it is, by default) and the table includes the
-
# +locking_column+ column (defaults to +lock_version+).
-
1
def locking_enabled?
-
lock_optimistically && columns_hash[locking_column]
-
end
-
-
# Set the column to use for optimistic locking. Defaults to +lock_version+.
-
1
def locking_column=(value)
-
@locking_column = value.to_s
-
end
-
-
# The version column used for optimistic locking. Defaults to +lock_version+.
-
1
def locking_column
-
reset_locking_column unless defined?(@locking_column)
-
@locking_column
-
end
-
-
# Quote the column name used for optimistic locking.
-
1
def quoted_locking_column
-
connection.quote_column_name(locking_column)
-
end
-
-
# Reset the column used for optimistic locking back to the +lock_version+ default.
-
1
def reset_locking_column
-
self.locking_column = DEFAULT_LOCKING_COLUMN
-
end
-
-
# Make sure the lock version column gets updated when counters are
-
# updated.
-
1
def update_counters(id, counters)
-
counters = counters.merge(locking_column => 1) if locking_enabled?
-
super
-
end
-
-
1
def column_defaults
-
@column_defaults ||= begin
-
defaults = super
-
-
if defaults.key?(locking_column) && lock_optimistically
-
defaults[locking_column] ||= 0
-
end
-
-
defaults
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Locking
-
# Locking::Pessimistic provides support for row-level locking using
-
# SELECT ... FOR UPDATE and other lock types.
-
#
-
# Pass <tt>:lock => true</tt> to <tt>ActiveRecord::Base.find</tt> to obtain an exclusive
-
# lock on the selected rows:
-
# # select * from accounts where id=1 for update
-
# Account.find(1, :lock => true)
-
#
-
# Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
-
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
-
#
-
# Account.transaction do
-
# # select * from accounts where name = 'shugo' limit 1 for update
-
# shugo = Account.where("name = 'shugo'").lock(true).first
-
# yuko = Account.where("name = 'yuko'").lock(true).first
-
# shugo.balance -= 100
-
# shugo.save!
-
# yuko.balance += 100
-
# yuko.save!
-
# end
-
#
-
# You can also use <tt>ActiveRecord::Base#lock!</tt> method to lock one record by id.
-
# This may be better if you don't need to lock every row. Example:
-
#
-
# Account.transaction do
-
# # select * from accounts where ...
-
# accounts = Account.where(...).all
-
# account1 = accounts.detect { |account| ... }
-
# account2 = accounts.detect { |account| ... }
-
# # select * from accounts where id=? for update
-
# account1.lock!
-
# account2.lock!
-
# account1.balance -= 100
-
# account1.save!
-
# account2.balance += 100
-
# account2.save!
-
# end
-
#
-
# You can start a transaction and acquire the lock in one go by calling
-
# <tt>with_lock</tt> with a block. The block is called from within
-
# a transaction, the object is already locked. Example:
-
#
-
# account = Account.first
-
# account.with_lock do
-
# # This block is called within a transaction,
-
# # account is already locked.
-
# account.balance -= 100
-
# account.save!
-
# end
-
#
-
# Database-specific information on row locking:
-
# MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
-
# PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
-
1
module Pessimistic
-
# Obtain a row lock on this record. Reloads the record to obtain the requested
-
# lock. Pass an SQL locking clause to append the end of the SELECT statement
-
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
-
# the locked record.
-
1
def lock!(lock = true)
-
reload(:lock => lock) if persisted?
-
self
-
end
-
-
# Wraps the passed block in a transaction, locking the object
-
# before yielding. You pass can the SQL locking clause
-
# as argument (see <tt>lock!</tt>).
-
1
def with_lock(lock = true)
-
transaction do
-
lock!(lock)
-
yield
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class LogSubscriber < ActiveSupport::LogSubscriber
-
1
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
-
-
1
def self.runtime=(value)
-
Thread.current[:active_record_sql_runtime] = value
-
end
-
-
1
def self.runtime
-
Thread.current[:active_record_sql_runtime] ||= 0
-
end
-
-
1
def self.reset_runtime
-
rt, self.runtime = runtime, 0
-
rt
-
end
-
-
1
def initialize
-
1
super
-
1
@odd_or_even = false
-
end
-
-
1
def sql(event)
-
self.class.runtime += event.duration
-
return unless logger.debug?
-
-
payload = event.payload
-
-
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
-
-
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
-
sql = payload[:sql].squeeze(' ')
-
binds = nil
-
-
unless (payload[:binds] || []).empty?
-
binds = " " + payload[:binds].map { |col,v|
-
[col.name, v]
-
}.inspect
-
end
-
-
if odd?
-
name = color(name, CYAN, true)
-
sql = color(sql, nil, true)
-
else
-
name = color(name, MAGENTA, true)
-
end
-
-
debug " #{name} #{sql}#{binds}"
-
end
-
-
1
def identity(event)
-
return unless logger.debug?
-
-
name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
-
line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
-
-
debug " #{name} #{line}"
-
end
-
-
1
def odd?
-
@odd_or_even = !@odd_or_even
-
end
-
-
1
def logger
-
14
ActiveRecord::Base.logger
-
end
-
end
-
end
-
-
1
ActiveRecord::LogSubscriber.attach_to :active_record
-
1
require "active_support/core_ext/class/attribute_accessors"
-
1
require 'set'
-
-
1
module ActiveRecord
-
# Exception that can be raised to stop migrations from going backwards.
-
1
class IrreversibleMigration < ActiveRecordError
-
end
-
-
1
class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
-
1
def initialize(version)
-
super("Multiple migrations have the version number #{version}")
-
end
-
end
-
-
1
class DuplicateMigrationNameError < ActiveRecordError#:nodoc:
-
1
def initialize(name)
-
super("Multiple migrations have the name #{name}")
-
end
-
end
-
-
1
class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
-
1
def initialize(version)
-
super("No migration with version number #{version}")
-
end
-
end
-
-
1
class IllegalMigrationNameError < ActiveRecordError#:nodoc:
-
1
def initialize(name)
-
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
-
end
-
end
-
-
1
class PendingMigrationError < ActiveRecordError#:nodoc:
-
1
def initialize
-
super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{ENV['RAILS_ENV']}' to resolve this issue.")
-
end
-
end
-
-
# = Active Record Migrations
-
#
-
# Migrations can manage the evolution of a schema used by several physical
-
# databases. It's a solution to the common problem of adding a field to make
-
# a new feature work in your local database, but being unsure of how to
-
# push that change to other developers and to the production server. With
-
# migrations, you can describe the transformations in self-contained classes
-
# that can be checked into version control systems and executed against
-
# another database that might be one, two, or five versions behind.
-
#
-
# Example of a simple migration:
-
#
-
# class AddSsl < ActiveRecord::Migration
-
# def up
-
# add_column :accounts, :ssl_enabled, :boolean, default: true
-
# end
-
#
-
# def down
-
# remove_column :accounts, :ssl_enabled
-
# end
-
# end
-
#
-
# This migration will add a boolean flag to the accounts table and remove it
-
# if you're backing out of the migration. It shows how all migrations have
-
# two methods +up+ and +down+ that describes the transformations
-
# required to implement or remove the migration. These methods can consist
-
# of both the migration specific methods like +add_column+ and +remove_column+,
-
# but may also contain regular Ruby code for generating data needed for the
-
# transformations.
-
#
-
# Example of a more complex migration that also needs to initialize data:
-
#
-
# class AddSystemSettings < ActiveRecord::Migration
-
# def up
-
# create_table :system_settings do |t|
-
# t.string :name
-
# t.string :label
-
# t.text :value
-
# t.string :type
-
# t.integer :position
-
# end
-
#
-
# SystemSetting.create name: 'notice',
-
# label: 'Use notice?',
-
# value: 1
-
# end
-
#
-
# def down
-
# drop_table :system_settings
-
# end
-
# end
-
#
-
# This migration first adds the +system_settings+ table, then creates the very
-
# first row in it using the Active Record model that relies on the table. It
-
# also uses the more advanced +create_table+ syntax where you can specify a
-
# complete table schema in one block call.
-
#
-
# == Available transformations
-
#
-
# * <tt>create_table(name, options)</tt>: Creates a table called +name+ and
-
# makes the table object available to a block that can then add columns to it,
-
# following the same format as +add_column+. See example above. The options hash
-
# is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
-
# table definition.
-
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
-
# * <tt>change_table(name, options)</tt>: Allows to make column alterations to
-
# the table called +name+. It makes the table object availabe to a block that
-
# can then add/remove columns, indexes or foreign keys to it.
-
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
-
# to +new_name+.
-
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
-
# to the table called +table_name+
-
# named +column_name+ specified to be one of the following types:
-
# <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
-
# <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
-
# <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
-
# specified by passing an +options+ hash like <tt>{ default: 11 }</tt>.
-
# Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
-
# <tt>{ limit: 50, null: false }</tt>) -- see
-
# ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
-
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
-
# a column but keeps the type and content.
-
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
-
# the column to a different type using the same parameters as add_column.
-
# * <tt>remove_column(table_name, column_names)</tt>: Removes the column listed in
-
# +column_names+ from the table called +table_name+.
-
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
-
# with the name of the column. Other options include
-
# <tt>:name</tt>, <tt>:unique</tt> (e.g.
-
# <tt>{ name: 'users_name_index', unique: true }</tt>) and <tt>:order</tt>
-
# (e.g. <tt>{ order: { name: :desc } }</tt>).
-
# * <tt>remove_index(table_name, column: column_name)</tt>: Removes the index
-
# specified by +column_name+.
-
# * <tt>remove_index(table_name, name: index_name)</tt>: Removes the index
-
# specified by +index_name+.
-
#
-
# == Irreversible transformations
-
#
-
# Some transformations are destructive in a manner that cannot be reversed.
-
# Migrations of that kind should raise an <tt>ActiveRecord::IrreversibleMigration</tt>
-
# exception in their +down+ method.
-
#
-
# == Running migrations from within Rails
-
#
-
# The Rails package has several tools to help create and apply migrations.
-
#
-
# To generate a new migration, you can use
-
# rails generate migration MyNewMigration
-
#
-
# where MyNewMigration is the name of your migration. The generator will
-
# create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
-
# in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
-
# UTC formatted date and time that the migration was generated.
-
#
-
# You may then edit the <tt>up</tt> and <tt>down</tt> methods of
-
# MyNewMigration.
-
#
-
# There is a special syntactic shortcut to generate migrations that add fields to a table.
-
#
-
# rails generate migration add_fieldname_to_tablename fieldname:string
-
#
-
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
-
# class AddFieldnameToTablename < ActiveRecord::Migration
-
# def up
-
# add_column :tablenames, :fieldname, :string
-
# end
-
#
-
# def down
-
# remove_column :tablenames, :fieldname
-
# end
-
# end
-
#
-
# To run migrations against the currently configured database, use
-
# <tt>rake db:migrate</tt>. This will update the database by running all of the
-
# pending migrations, creating the <tt>schema_migrations</tt> table
-
# (see "About the schema_migrations table" section below) if missing. It will also
-
# invoke the db:schema:dump task, which will update your db/schema.rb file
-
# to match the structure of your database.
-
#
-
# To roll the database back to a previous migration version, use
-
# <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
-
# you wish to downgrade. If any of the migrations throw an
-
# <tt>ActiveRecord::IrreversibleMigration</tt> exception, that step will fail and you'll
-
# have some manual work to do.
-
#
-
# == Database support
-
#
-
# Migrations are currently supported in MySQL, PostgreSQL, SQLite,
-
# SQL Server, Sybase, and Oracle (all supported databases except DB2).
-
#
-
# == More examples
-
#
-
# Not all migrations change the schema. Some just fix the data:
-
#
-
# class RemoveEmptyTags < ActiveRecord::Migration
-
# def up
-
# Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
-
# end
-
#
-
# def down
-
# # not much we can do to restore deleted data
-
# raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
-
# end
-
# end
-
#
-
# Others remove columns when they migrate up instead of down:
-
#
-
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
-
# def up
-
# remove_column :items, :incomplete_items_count
-
# remove_column :items, :completed_items_count
-
# end
-
#
-
# def down
-
# add_column :items, :incomplete_items_count
-
# add_column :items, :completed_items_count
-
# end
-
# end
-
#
-
# And sometimes you need to do something in SQL not abstracted directly by migrations:
-
#
-
# class MakeJoinUnique < ActiveRecord::Migration
-
# def up
-
# execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
-
# end
-
#
-
# def down
-
# execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
-
# end
-
# end
-
#
-
# == Using a model after changing its table
-
#
-
# Sometimes you'll want to add a column in a migration and populate it
-
# immediately after. In that case, you'll need to make a call to
-
# <tt>Base#reset_column_information</tt> in order to ensure that the model has the
-
# latest column data from after the new column was added. Example:
-
#
-
# class AddPeopleSalary < ActiveRecord::Migration
-
# def up
-
# add_column :people, :salary, :integer
-
# Person.reset_column_information
-
# Person.all.each do |p|
-
# p.update_attribute :salary, SalaryCalculator.compute(p)
-
# end
-
# end
-
# end
-
#
-
# == Controlling verbosity
-
#
-
# By default, migrations will describe the actions they are taking, writing
-
# them to the console as they happen, along with benchmarks describing how
-
# long each step took.
-
#
-
# You can quiet them down by setting ActiveRecord::Migration.verbose = false.
-
#
-
# You can also insert your own messages and benchmarks by using the +say_with_time+
-
# method:
-
#
-
# def up
-
# ...
-
# say_with_time "Updating salaries..." do
-
# Person.all.each do |p|
-
# p.update_attribute :salary, SalaryCalculator.compute(p)
-
# end
-
# end
-
# ...
-
# end
-
#
-
# The phrase "Updating salaries..." would then be printed, along with the
-
# benchmark for the block when the block completes.
-
#
-
# == About the schema_migrations table
-
#
-
# Rails versions 2.0 and prior used to create a table called
-
# <tt>schema_info</tt> when using migrations. This table contained the
-
# version of the schema as of the last applied migration.
-
#
-
# Starting with Rails 2.1, the <tt>schema_info</tt> table is
-
# (automatically) replaced by the <tt>schema_migrations</tt> table, which
-
# contains the version numbers of all the migrations applied.
-
#
-
# As a result, it is now possible to add migration files that are numbered
-
# lower than the current schema version: when migrating up, those
-
# never-applied "interleaved" migrations will be automatically applied, and
-
# when migrating down, never-applied "interleaved" migrations will be skipped.
-
#
-
# == Timestamped Migrations
-
#
-
# By default, Rails generates migrations that look like:
-
#
-
# 20080717013526_your_migration_name.rb
-
#
-
# The prefix is a generation timestamp (in UTC).
-
#
-
# If you'd prefer to use numeric prefixes, you can turn timestamped migrations
-
# off by setting:
-
#
-
# config.active_record.timestamped_migrations = false
-
#
-
# In application.rb.
-
#
-
# == Reversible Migrations
-
#
-
# Starting with Rails 3.1, you will be able to define reversible migrations.
-
# Reversible migrations are migrations that know how to go +down+ for you.
-
# You simply supply the +up+ logic, and the Migration system will figure out
-
# how to execute the down commands for you.
-
#
-
# To define a reversible migration, define the +change+ method in your
-
# migration like this:
-
#
-
# class TenderloveMigration < ActiveRecord::Migration
-
# def change
-
# create_table(:horses) do |t|
-
# t.column :content, :text
-
# t.column :remind_at, :datetime
-
# end
-
# end
-
# end
-
#
-
# This migration will create the horses table for you on the way up, and
-
# automatically figure out how to drop the table on the way down.
-
#
-
# Some commands like +remove_column+ cannot be reversed. If you care to
-
# define how to move up and down in these cases, you should define the +up+
-
# and +down+ methods as before.
-
#
-
# If a command cannot be reversed, an
-
# <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
-
# the migration is moving down.
-
#
-
# For a list of commands that are reversible, please see
-
# <tt>ActiveRecord::Migration::CommandRecorder</tt>.
-
1
class Migration
-
1
autoload :CommandRecorder, 'active_record/migration/command_recorder'
-
-
-
# This class is used to verify that all migrations have been run before
-
# loading a web page if config.active_record.migration_error is set to :page_load
-
1
class CheckPending
-
1
def initialize(app)
-
@app = app
-
end
-
-
1
def call(env)
-
ActiveRecord::Base.logger.quietly do
-
ActiveRecord::Migration.check_pending!
-
end
-
@app.call(env)
-
end
-
end
-
-
1
class << self
-
1
attr_accessor :delegate # :nodoc:
-
end
-
-
1
def self.check_pending!
-
raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?
-
end
-
-
1
def self.method_missing(name, *args, &block) # :nodoc:
-
(delegate || superclass.delegate).send(name, *args, &block)
-
end
-
-
1
def self.migrate(direction)
-
new.migrate direction
-
end
-
-
1
cattr_accessor :verbose
-
-
1
attr_accessor :name, :version
-
-
1
def initialize(name = self.class.name, version = nil)
-
1
@name = name
-
1
@version = version
-
1
@connection = nil
-
1
@reverting = false
-
end
-
-
# instantiate the delegate object after initialize is defined
-
1
self.verbose = true
-
1
self.delegate = new
-
-
1
def revert
-
@reverting = true
-
yield
-
ensure
-
@reverting = false
-
end
-
-
1
def reverting?
-
@reverting
-
end
-
-
1
def up
-
self.class.delegate = self
-
return unless self.class.respond_to?(:up)
-
self.class.up
-
end
-
-
1
def down
-
self.class.delegate = self
-
return unless self.class.respond_to?(:down)
-
self.class.down
-
end
-
-
# Execute this migration in the named direction
-
1
def migrate(direction)
-
return unless respond_to?(direction)
-
-
case direction
-
when :up then announce "migrating"
-
when :down then announce "reverting"
-
end
-
-
time = nil
-
ActiveRecord::Base.connection_pool.with_connection do |conn|
-
@connection = conn
-
if respond_to?(:change)
-
if direction == :down
-
recorder = CommandRecorder.new(@connection)
-
suppress_messages do
-
@connection = recorder
-
change
-
end
-
@connection = conn
-
time = Benchmark.measure {
-
self.revert {
-
recorder.inverse.each do |cmd, args|
-
send(cmd, *args)
-
end
-
}
-
}
-
else
-
time = Benchmark.measure { change }
-
end
-
else
-
time = Benchmark.measure { send(direction) }
-
end
-
@connection = nil
-
end
-
-
case direction
-
when :up then announce "migrated (%.4fs)" % time.real; write
-
when :down then announce "reverted (%.4fs)" % time.real; write
-
end
-
end
-
-
1
def write(text="")
-
puts(text) if verbose
-
end
-
-
1
def announce(message)
-
text = "#{version} #{name}: #{message}"
-
length = [0, 75 - text.length].max
-
write "== %s %s" % [text, "=" * length]
-
end
-
-
1
def say(message, subitem=false)
-
write "#{subitem ? " ->" : "--"} #{message}"
-
end
-
-
1
def say_with_time(message)
-
say(message)
-
result = nil
-
time = Benchmark.measure { result = yield }
-
say "%.4fs" % time.real, :subitem
-
say("#{result} rows", :subitem) if result.is_a?(Integer)
-
result
-
end
-
-
1
def suppress_messages
-
save, self.verbose = verbose, false
-
yield
-
ensure
-
self.verbose = save
-
end
-
-
1
def connection
-
@connection || ActiveRecord::Base.connection
-
end
-
-
1
def method_missing(method, *arguments, &block)
-
arg_list = arguments.map{ |a| a.inspect } * ', '
-
-
say_with_time "#{method}(#{arg_list})" do
-
unless reverting?
-
unless arguments.empty? || method == :execute
-
arguments[0] = Migrator.proper_table_name(arguments.first)
-
arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
-
end
-
end
-
return super unless connection.respond_to?(method)
-
connection.send(method, *arguments, &block)
-
end
-
end
-
-
1
def copy(destination, sources, options = {})
-
copied = []
-
-
FileUtils.mkdir_p(destination) unless File.exists?(destination)
-
-
destination_migrations = ActiveRecord::Migrator.migrations(destination)
-
last = destination_migrations.last
-
sources.each do |scope, path|
-
source_migrations = ActiveRecord::Migrator.migrations(path)
-
-
source_migrations.each do |migration|
-
source = File.read(migration.filename)
-
source = "# This migration comes from #{scope} (originally #{migration.version})\n#{source}"
-
-
if duplicate = destination_migrations.detect { |m| m.name == migration.name }
-
if options[:on_skip] && duplicate.scope != scope.to_s
-
options[:on_skip].call(scope, migration)
-
end
-
next
-
end
-
-
migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
-
new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
-
old_path, migration.filename = migration.filename, new_path
-
last = migration
-
-
File.open(migration.filename, "w") { |f| f.write source }
-
copied << migration
-
options[:on_copy].call(scope, migration, old_path) if options[:on_copy]
-
destination_migrations << migration
-
end
-
end
-
-
copied
-
end
-
-
1
def next_migration_number(number)
-
if ActiveRecord::Base.timestamped_migrations
-
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
-
else
-
"%.3d" % number
-
end
-
end
-
end
-
-
# MigrationProxy is used to defer loading of the actual migration classes
-
# until they are needed
-
1
class MigrationProxy < Struct.new(:name, :version, :filename, :scope)
-
-
1
def initialize(name, version, filename, scope)
-
super
-
@migration = nil
-
end
-
-
1
def basename
-
File.basename(filename)
-
end
-
-
1
delegate :migrate, :announce, :write, :to => :migration
-
-
1
private
-
-
1
def migration
-
@migration ||= load_migration
-
end
-
-
1
def load_migration
-
require(File.expand_path(filename))
-
name.constantize.new
-
end
-
-
end
-
-
1
class Migrator#:nodoc:
-
1
class << self
-
1
attr_writer :migrations_paths
-
1
alias :migrations_path= :migrations_paths=
-
-
1
def migrate(migrations_paths, target_version = nil, &block)
-
case
-
when target_version.nil?
-
up(migrations_paths, target_version, &block)
-
when current_version == 0 && target_version == 0
-
[]
-
when current_version > target_version
-
down(migrations_paths, target_version, &block)
-
else
-
up(migrations_paths, target_version, &block)
-
end
-
end
-
-
1
def rollback(migrations_paths, steps=1)
-
move(:down, migrations_paths, steps)
-
end
-
-
1
def forward(migrations_paths, steps=1)
-
move(:up, migrations_paths, steps)
-
end
-
-
1
def up(migrations_paths, target_version = nil)
-
migrations = migrations(migrations_paths)
-
migrations.select! { |m| yield m } if block_given?
-
-
self.new(:up, migrations, target_version).migrate
-
end
-
-
1
def down(migrations_paths, target_version = nil, &block)
-
migrations = migrations(migrations_paths)
-
migrations.select! { |m| yield m } if block_given?
-
-
self.new(:down, migrations, target_version).migrate
-
end
-
-
1
def run(direction, migrations_paths, target_version)
-
self.new(direction, migrations(migrations_paths), target_version).run
-
end
-
-
1
def open(migrations_paths)
-
self.new(:up, migrations(migrations_paths), nil)
-
end
-
-
1
def schema_migrations_table_name
-
SchemaMigration.table_name
-
end
-
-
1
def get_all_versions
-
SchemaMigration.all.map { |x| x.version.to_i }.sort
-
end
-
-
1
def current_version
-
sm_table = schema_migrations_table_name
-
if Base.connection.table_exists?(sm_table)
-
get_all_versions.max || 0
-
else
-
0
-
end
-
end
-
-
1
def needs_migration?
-
current_version < last_version
-
end
-
-
1
def last_version
-
migrations(migrations_paths).last.try(:version)||0
-
end
-
-
1
def proper_table_name(name)
-
# Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
-
if name.respond_to? :table_name
-
name.table_name
-
else
-
"#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
-
end
-
end
-
-
1
def migrations_paths
-
@migrations_paths ||= ['db/migrate']
-
# just to not break things if someone uses: migration_path = some_string
-
Array(@migrations_paths)
-
end
-
-
1
def migrations_path
-
migrations_paths.first
-
end
-
-
1
def migrations(paths)
-
paths = Array(paths)
-
-
files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
-
-
migrations = files.map do |file|
-
version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
-
-
raise IllegalMigrationNameError.new(file) unless version
-
version = version.to_i
-
name = name.camelize
-
-
MigrationProxy.new(name, version, file, scope)
-
end
-
-
migrations.sort_by(&:version)
-
end
-
-
1
private
-
-
1
def move(direction, migrations_paths, steps)
-
migrator = self.new(direction, migrations(migrations_paths))
-
start_index = migrator.migrations.index(migrator.current_migration)
-
-
if start_index
-
finish = migrator.migrations[start_index + steps]
-
version = finish ? finish.version : 0
-
send(direction, migrations_paths, version)
-
end
-
end
-
end
-
-
1
def initialize(direction, migrations, target_version = nil)
-
raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
-
-
@direction = direction
-
@target_version = target_version
-
@migrated_versions = nil
-
-
if Array(migrations).grep(String).empty?
-
@migrations = migrations
-
else
-
ActiveSupport::Deprecation.warn "instantiate this class with a list of migrations"
-
@migrations = self.class.migrations(migrations)
-
end
-
-
validate(@migrations)
-
-
ActiveRecord::SchemaMigration.create_table
-
end
-
-
1
def current_version
-
migrated.sort.last || 0
-
end
-
-
1
def current_migration
-
migrations.detect { |m| m.version == current_version }
-
end
-
1
alias :current :current_migration
-
-
1
def run
-
target = migrations.detect { |m| m.version == @target_version }
-
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
-
unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
-
target.migrate(@direction)
-
record_version_state_after_migrating(target.version)
-
end
-
end
-
-
1
def migrate
-
if !target && @target_version && @target_version > 0
-
raise UnknownMigrationVersionError.new(@target_version)
-
end
-
-
running = runnable
-
-
if block_given?
-
message = "block argument to migrate is deprecated, please filter migrations before constructing the migrator"
-
ActiveSupport::Deprecation.warn message
-
running.select! { |m| yield m }
-
end
-
-
running.each do |migration|
-
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
-
-
begin
-
ddl_transaction do
-
migration.migrate(@direction)
-
record_version_state_after_migrating(migration.version)
-
end
-
rescue => e
-
canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
-
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
-
end
-
end
-
end
-
-
1
def runnable
-
runnable = migrations[start..finish]
-
if up?
-
runnable.reject { |m| ran?(m) }
-
else
-
# skip the last migration if we're headed down, but not ALL the way down
-
runnable.pop if target
-
runnable.find_all { |m| ran?(m) }
-
end
-
end
-
-
1
def migrations
-
down? ? @migrations.reverse : @migrations.sort_by(&:version)
-
end
-
-
1
def pending_migrations
-
already_migrated = migrated
-
migrations.reject { |m| already_migrated.include?(m.version) }
-
end
-
-
1
def migrated
-
@migrated_versions ||= Set.new(self.class.get_all_versions)
-
end
-
-
1
private
-
1
def ran?(migration)
-
migrated.include?(migration.version.to_i)
-
end
-
-
1
def target
-
migrations.detect { |m| m.version == @target_version }
-
end
-
-
1
def finish
-
migrations.index(target) || migrations.size - 1
-
end
-
-
1
def start
-
up? ? 0 : (migrations.index(current) || 0)
-
end
-
-
1
def validate(migrations)
-
name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 }
-
raise DuplicateMigrationNameError.new(name) if name
-
-
version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 }
-
raise DuplicateMigrationVersionError.new(version) if version
-
end
-
-
1
def record_version_state_after_migrating(version)
-
if down?
-
migrated.delete(version)
-
ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
-
else
-
migrated << version
-
ActiveRecord::SchemaMigration.create!(:version => version.to_s)
-
end
-
end
-
-
1
def up?
-
@direction == :up
-
end
-
-
1
def down?
-
@direction == :down
-
end
-
-
# Wrap the migration in a transaction only if supported by the adapter.
-
1
def ddl_transaction
-
if Base.connection.supports_ddl_transactions?
-
Base.transaction { yield }
-
else
-
yield
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class Migration
-
1
module JoinTable #:nodoc:
-
1
private
-
-
1
def find_join_table_name(table_1, table_2, options = {})
-
options.delete(:table_name) || join_table_name(table_1, table_2)
-
end
-
-
1
def join_table_name(table_1, table_2)
-
[table_1, table_2].sort.join("_").to_sym
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ModelSchema
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
##
-
# :singleton-method:
-
# Accessor for the prefix type that will be prepended to every primary key column name.
-
# The options are :table_name and :table_name_with_underscore. If the first is specified,
-
# the Product class will look for "productid" instead of "id" as the primary column. If the
-
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
-
# that this is a global setting for all Active Records.
-
1
mattr_accessor :primary_key_prefix_type, instance_writer: false
-
-
##
-
# :singleton-method:
-
# Accessor for the name of the prefix string to prepend to every table name. So if set
-
# to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
-
# etc. This is a convenient way of creating a namespace for tables in a shared database.
-
# By default, the prefix is the empty string.
-
#
-
# If you are organising your models within modules you can add a prefix to the models within
-
# a namespace by defining a singleton method in the parent module called table_name_prefix which
-
# returns your chosen prefix.
-
1
class_attribute :table_name_prefix, instance_writer: false
-
1
self.table_name_prefix = ""
-
-
##
-
# :singleton-method:
-
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
-
# "people_basecamp"). By default, the suffix is the empty string.
-
1
class_attribute :table_name_suffix, instance_writer: false
-
1
self.table_name_suffix = ""
-
-
##
-
# :singleton-method:
-
# Indicates whether table names should be the pluralized versions of the corresponding class names.
-
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
-
# See table_name for the full rules on table/class naming. This is true, by default.
-
1
class_attribute :pluralize_table_names, instance_writer: false
-
1
self.pluralize_table_names = true
-
-
1
self.inheritance_column = 'type'
-
end
-
-
1
module ClassMethods
-
# Guesses the table name (in forced lower-case) based on the name of the class in the
-
# inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
-
# looks like: Reply < Message < ActiveRecord::Base, then Message is used
-
# to guess the table name even when called on Reply. The rules used to do the guess
-
# are handled by the Inflector class in Active Support, which knows almost all common
-
# English inflections. You can add new inflections in config/initializers/inflections.rb.
-
#
-
# Nested classes are given table names prefixed by the singular form of
-
# the parent's table name. Enclosing modules are not considered.
-
#
-
# ==== Examples
-
#
-
# class Invoice < ActiveRecord::Base
-
# end
-
#
-
# file class table_name
-
# invoice.rb Invoice invoices
-
#
-
# class Invoice < ActiveRecord::Base
-
# class Lineitem < ActiveRecord::Base
-
# end
-
# end
-
#
-
# file class table_name
-
# invoice.rb Invoice::Lineitem invoice_lineitems
-
#
-
# module Invoice
-
# class Lineitem < ActiveRecord::Base
-
# end
-
# end
-
#
-
# file class table_name
-
# invoice/lineitem.rb Invoice::Lineitem lineitems
-
#
-
# Additionally, the class-level +table_name_prefix+ is prepended and the
-
# +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
-
# the table name guess for an Invoice class becomes "myapp_invoices".
-
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
-
#
-
# You can also set your own table name explicitly:
-
#
-
# class Mouse < ActiveRecord::Base
-
# self.table_name = "mice"
-
# end
-
#
-
# Alternatively, you can override the table_name method to define your
-
# own computation. (Possibly using <tt>super</tt> to manipulate the default
-
# table name.) Example:
-
#
-
# class Post < ActiveRecord::Base
-
# def self.table_name
-
# "special_" + super
-
# end
-
# end
-
# Post.table_name # => "special_posts"
-
1
def table_name
-
1
reset_table_name unless defined?(@table_name)
-
1
@table_name
-
end
-
-
# Sets the table name explicitly. Example:
-
#
-
# class Project < ActiveRecord::Base
-
# self.table_name = "project"
-
# end
-
#
-
# You can also just define your own <tt>self.table_name</tt> method; see
-
# the documentation for ActiveRecord::Base#table_name.
-
1
def table_name=(value)
-
1
value = value && value.to_s
-
-
1
if defined?(@table_name)
-
return if value == @table_name
-
reset_column_information if connected?
-
end
-
-
1
@table_name = value
-
1
@quoted_table_name = nil
-
1
@arel_table = nil
-
1
@sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
-
1
@relation = Relation.new(self, arel_table)
-
end
-
-
# Returns a quoted version of the table name, used to construct SQL statements.
-
1
def quoted_table_name
-
@quoted_table_name ||= connection.quote_table_name(table_name)
-
end
-
-
# Computes the table name, (re)sets it internally, and returns it.
-
1
def reset_table_name #:nodoc:
-
self.table_name = if abstract_class?
-
superclass == Base ? nil : superclass.table_name
-
elsif superclass.abstract_class?
-
superclass.table_name || compute_table_name
-
else
-
compute_table_name
-
end
-
end
-
-
1
def full_table_name_prefix #:nodoc:
-
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
-
end
-
-
# Defines the name of the table column which will store the class name on single-table
-
# inheritance situations.
-
#
-
# The default inheritance column name is +type+, which means it's a
-
# reserved word inside Active Record. To be able to use single-table
-
# inheritance with another column name, or to use the column +type+ in
-
# your own model for something else, you can set +inheritance_column+:
-
#
-
# self.inheritance_column = 'zoink'
-
1
def inheritance_column
-
(@inheritance_column ||= nil) || superclass.inheritance_column
-
end
-
-
# Sets the value of inheritance_column
-
1
def inheritance_column=(value)
-
1
@inheritance_column = value.to_s
-
1
@explicit_inheritance_column = true
-
end
-
-
1
def sequence_name
-
if base_class == self
-
@sequence_name ||= reset_sequence_name
-
else
-
(@sequence_name ||= nil) || base_class.sequence_name
-
end
-
end
-
-
1
def reset_sequence_name #:nodoc:
-
@explicit_sequence_name = false
-
@sequence_name = connection.default_sequence_name(table_name, primary_key)
-
end
-
-
# Sets the name of the sequence to use when generating ids to the given
-
# value, or (if the value is nil or false) to the value returned by the
-
# given block. This is required for Oracle and is useful for any
-
# database which relies on sequences for primary key generation.
-
#
-
# If a sequence name is not explicitly set when using Oracle or Firebird,
-
# it will default to the commonly used pattern of: #{table_name}_seq
-
#
-
# If a sequence name is not explicitly set when using PostgreSQL, it
-
# will discover the sequence corresponding to your primary key for you.
-
#
-
# class Project < ActiveRecord::Base
-
# self.sequence_name = "projectseq" # default would have been "project_seq"
-
# end
-
1
def sequence_name=(value)
-
1
@sequence_name = value.to_s
-
1
@explicit_sequence_name = true
-
end
-
-
# Indicates whether the table associated with this class exists
-
1
def table_exists?
-
connection.schema_cache.table_exists?(table_name)
-
end
-
-
# Returns an array of column objects for the table associated with this class.
-
1
def columns
-
@columns ||= connection.schema_cache.columns[table_name].map do |col|
-
col = col.dup
-
col.primary = (col.name == primary_key)
-
col
-
end
-
end
-
-
# Returns a hash of column objects for the table associated with this class.
-
1
def columns_hash
-
@columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
-
end
-
-
1
def column_types # :nodoc:
-
@column_types ||= decorate_columns(columns_hash.dup)
-
end
-
-
1
def decorate_columns(columns_hash) # :nodoc:
-
return if columns_hash.empty?
-
-
serialized_attributes.each_key do |key|
-
columns_hash[key] = AttributeMethods::Serialization::Type.new(columns_hash[key])
-
end
-
-
columns_hash.each do |name, col|
-
if create_time_zone_conversion_attribute?(name, col)
-
columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col)
-
end
-
end
-
-
columns_hash
-
end
-
-
# Returns a hash where the keys are column names and the values are
-
# default values when instantiating the AR object for this table.
-
1
def column_defaults
-
@column_defaults ||= Hash[columns.map { |c| [c.name, c.default] }]
-
end
-
-
# Returns an array of column names as strings.
-
1
def column_names
-
@column_names ||= columns.map { |column| column.name }
-
end
-
-
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
-
# and columns used for single table inheritance have been removed.
-
1
def content_columns
-
@content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
-
end
-
-
# Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
-
# and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
-
# is available.
-
1
def column_methods_hash #:nodoc:
-
@dynamic_methods_hash ||= column_names.each_with_object(Hash.new(false)) do |attr, methods|
-
attr_name = attr.to_s
-
methods[attr.to_sym] = attr_name
-
methods["#{attr}=".to_sym] = attr_name
-
methods["#{attr}?".to_sym] = attr_name
-
methods["#{attr}_before_type_cast".to_sym] = attr_name
-
end
-
end
-
-
# Resets all the cached information about columns, which will cause them
-
# to be reloaded on the next request.
-
#
-
# The most common usage pattern for this method is probably in a migration,
-
# when just after creating a table you want to populate it with some default
-
# values, eg:
-
#
-
# class CreateJobLevels < ActiveRecord::Migration
-
# def up
-
# create_table :job_levels do |t|
-
# t.integer :id
-
# t.string :name
-
#
-
# t.timestamps
-
# end
-
#
-
# JobLevel.reset_column_information
-
# %w{assistant executive manager director}.each do |type|
-
# JobLevel.create(:name => type)
-
# end
-
# end
-
#
-
# def down
-
# drop_table :job_levels
-
# end
-
# end
-
1
def reset_column_information
-
connection.clear_cache!
-
undefine_attribute_methods
-
connection.schema_cache.clear_table_cache!(table_name) if table_exists?
-
-
@arel_engine = nil
-
@column_defaults = nil
-
@column_names = nil
-
@columns = nil
-
@columns_hash = nil
-
@column_types = nil
-
@content_columns = nil
-
@dynamic_methods_hash = nil
-
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
-
@relation = nil
-
end
-
-
# This is a hook for use by modules that need to do extra stuff to
-
# attributes when they are initialized. (e.g. attribute
-
# serialization)
-
1
def initialize_attributes(attributes, options = {}) #:nodoc:
-
attributes
-
end
-
-
1
private
-
-
# Guesses the table name, but does not decorate it with prefix and suffix information.
-
1
def undecorated_table_name(class_name = base_class.name)
-
table_name = class_name.to_s.demodulize.underscore
-
pluralize_table_names ? table_name.pluralize : table_name
-
end
-
-
# Computes and returns a table name according to default conventions.
-
1
def compute_table_name
-
base = base_class
-
if self == base
-
# Nested classes are prefixed with singular parent table name.
-
if parent < Base && !parent.abstract_class?
-
contained = parent.table_name
-
contained = contained.singularize if parent.pluralize_table_names
-
contained += '_'
-
end
-
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
-
else
-
# STI subclasses always use their superclass' table.
-
base.table_name
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/object/try'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActiveRecord
-
1
module NestedAttributes #:nodoc:
-
1
class TooManyRecords < ActiveRecordError
-
end
-
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :nested_attributes_options, instance_writer: false
-
1
self.nested_attributes_options = {}
-
end
-
-
# = Active Record Nested Attributes
-
#
-
# Nested attributes allow you to save attributes on associated records
-
# through the parent. By default nested attribute updating is turned off
-
# and you can enable it using the accepts_nested_attributes_for class
-
# method. When you enable nested attributes an attribute writer is
-
# defined on the model.
-
#
-
# The attribute writer is named after the association, which means that
-
# in the following example, two new methods are added to your model:
-
#
-
# <tt>author_attributes=(attributes)</tt> and
-
# <tt>pages_attributes=(attributes)</tt>.
-
#
-
# class Book < ActiveRecord::Base
-
# has_one :author
-
# has_many :pages
-
#
-
# accepts_nested_attributes_for :author, :pages
-
# end
-
#
-
# Note that the <tt>:autosave</tt> option is automatically enabled on every
-
# association that accepts_nested_attributes_for is used for.
-
#
-
# === One-to-one
-
#
-
# Consider a Member model that has one Avatar:
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar
-
# end
-
#
-
# Enabling nested attributes on a one-to-one association allows you to
-
# create the member and avatar in one go:
-
#
-
# params = { :member => { :name => 'Jack', :avatar_attributes => { :icon => 'smiling' } } }
-
# member = Member.create(params[:member])
-
# member.avatar.id # => 2
-
# member.avatar.icon # => 'smiling'
-
#
-
# It also allows you to update the avatar through the member:
-
#
-
# params = { :member => { :avatar_attributes => { :id => '2', :icon => 'sad' } } }
-
# member.update_attributes params[:member]
-
# member.avatar.icon # => 'sad'
-
#
-
# By default you will only be able to set and update attributes on the
-
# associated model. If you want to destroy the associated model through the
-
# attributes hash, you have to enable it first using the
-
# <tt>:allow_destroy</tt> option.
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar, :allow_destroy => true
-
# end
-
#
-
# Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
-
# value that evaluates to +true+, you will destroy the associated model:
-
#
-
# member.avatar_attributes = { :id => '2', :_destroy => '1' }
-
# member.avatar.marked_for_destruction? # => true
-
# member.save
-
# member.reload.avatar # => nil
-
#
-
# Note that the model will _not_ be destroyed until the parent is saved.
-
#
-
# === One-to-many
-
#
-
# Consider a member that has a number of posts:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts
-
# end
-
#
-
# You can now set or update attributes on an associated post model through
-
# the attribute hash.
-
#
-
# For each hash that does _not_ have an <tt>id</tt> key a new record will
-
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
-
# that evaluates to +true+.
-
#
-
# params = { :member => {
-
# :name => 'joe', :posts_attributes => [
-
# { :title => 'Kari, the awesome Ruby documentation browser!' },
-
# { :title => 'The egalitarian assumption of the modern citizen' },
-
# { :title => '', :_destroy => '1' } # this will be ignored
-
# ]
-
# }}
-
#
-
# member = Member.create(params[:member])
-
# member.posts.length # => 2
-
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
-
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
-
#
-
# You may also set a :reject_if proc to silently ignore any new record
-
# hashes if they fail to pass your criteria. For example, the previous
-
# example could be rewritten as:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
-
# end
-
#
-
# params = { :member => {
-
# :name => 'joe', :posts_attributes => [
-
# { :title => 'Kari, the awesome Ruby documentation browser!' },
-
# { :title => 'The egalitarian assumption of the modern citizen' },
-
# { :title => '' } # this will be ignored because of the :reject_if proc
-
# ]
-
# }}
-
#
-
# member = Member.create(params[:member])
-
# member.posts.length # => 2
-
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
-
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
-
#
-
# Alternatively, :reject_if also accepts a symbol for using methods:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, :reject_if => :new_record?
-
# end
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, :reject_if => :reject_posts
-
#
-
# def reject_posts(attributed)
-
# attributed['title'].blank?
-
# end
-
# end
-
#
-
# If the hash contains an <tt>id</tt> key that matches an already
-
# associated record, the matching record will be modified:
-
#
-
# member.attributes = {
-
# :name => 'Joe',
-
# :posts_attributes => [
-
# { :id => 1, :title => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
-
# { :id => 2, :title => '[UPDATED] other post' }
-
# ]
-
# }
-
#
-
# member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
-
# member.posts.second.title # => '[UPDATED] other post'
-
#
-
# By default the associated records are protected from being destroyed. If
-
# you want to destroy any of the associated records through the attributes
-
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
-
# option. This will allow you to also use the <tt>_destroy</tt> key to
-
# destroy existing records:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, :allow_destroy => true
-
# end
-
#
-
# params = { :member => {
-
# :posts_attributes => [{ :id => '2', :_destroy => '1' }]
-
# }}
-
#
-
# member.attributes = params[:member]
-
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
-
# member.posts.length # => 2
-
# member.save
-
# member.reload.posts.length # => 1
-
#
-
# === Saving
-
#
-
# All changes to models, including the destruction of those marked for
-
# destruction, are saved and destroyed automatically and atomically when
-
# the parent model is saved. This happens inside the transaction initiated
-
# by the parents save method. See ActiveRecord::AutosaveAssociation.
-
#
-
# === Validating the presence of a parent model
-
#
-
# If you want to validate that a child record is associated with a parent
-
# record, you can use <tt>validates_presence_of</tt> and
-
# <tt>inverse_of</tt> as this example illustrates:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts, :inverse_of => :member
-
# accepts_nested_attributes_for :posts
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# belongs_to :member, :inverse_of => :posts
-
# validates_presence_of :member
-
# end
-
1
module ClassMethods
-
1
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
-
-
# Defines an attributes writer for the specified association(s).
-
#
-
# Supported options:
-
# [:allow_destroy]
-
# If true, destroys any members from the attributes hash with a
-
# <tt>_destroy</tt> key and a value that evaluates to +true+
-
# (eg. 1, '1', true, or 'true'). This option is off by default.
-
# [:reject_if]
-
# Allows you to specify a Proc or a Symbol pointing to a method
-
# that checks whether a record should be built for a certain attribute
-
# hash. The hash is passed to the supplied Proc or the method
-
# and it should return either +true+ or +false+. When no :reject_if
-
# is specified, a record will be built for all attribute hashes that
-
# do not have a <tt>_destroy</tt> value that evaluates to true.
-
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
-
# that will reject a record where all the attributes are blank excluding
-
# any value for _destroy.
-
# [:limit]
-
# Allows you to specify the maximum number of the associated records that
-
# can be processed with the nested attributes. Limit also can be specified as a
-
# Proc or a Symbol pointing to a method that should return number. If the size of the
-
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
-
# exception is raised. If omitted, any number associations can be processed.
-
# Note that the :limit option is only applicable to one-to-many associations.
-
# [:update_only]
-
# For a one-to-one association, this option allows you to specify how
-
# nested attributes are to be used when an associated record already
-
# exists. In general, an existing record may either be updated with the
-
# new set of attribute values or be replaced by a wholly new record
-
# containing those values. By default the :update_only option is +false+
-
# and the nested attributes are used to update the existing record only
-
# if they include the record's <tt>:id</tt> value. Otherwise a new
-
# record will be instantiated and used to replace the existing one.
-
# However if the :update_only option is +true+, the nested attributes
-
# are used to update the record's attributes always, regardless of
-
# whether the <tt>:id</tt> is present. The option is ignored for collection
-
# associations.
-
#
-
# Examples:
-
# # creates avatar_attributes=
-
# accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes['name'].blank? }
-
# # creates avatar_attributes=
-
# accepts_nested_attributes_for :avatar, :reject_if => :all_blank
-
# # creates avatar_attributes= and posts_attributes=
-
# accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true
-
1
def accepts_nested_attributes_for(*attr_names)
-
options = { :allow_destroy => false, :update_only => false }
-
options.update(attr_names.extract_options!)
-
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
-
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
-
-
attr_names.each do |association_name|
-
if reflection = reflect_on_association(association_name)
-
reflection.options[:autosave] = true
-
add_autosave_association_callbacks(reflection)
-
-
nested_attributes_options = self.nested_attributes_options.dup
-
nested_attributes_options[association_name.to_sym] = options
-
self.nested_attributes_options = nested_attributes_options
-
-
type = (reflection.collection? ? :collection : :one_to_one)
-
-
# def pirate_attributes=(attributes)
-
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
-
# end
-
generated_feature_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
-
if method_defined?(:#{association_name}_attributes=)
-
remove_method(:#{association_name}_attributes=)
-
end
-
def #{association_name}_attributes=(attributes)
-
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
-
end
-
eoruby
-
else
-
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
-
end
-
end
-
end
-
end
-
-
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
-
# used in conjunction with fields_for to build a form element for the
-
# destruction of this association.
-
#
-
# See ActionView::Helpers::FormHelper::fields_for for more info.
-
1
def _destroy
-
marked_for_destruction?
-
end
-
-
1
private
-
-
# Attribute hash keys that should not be assigned as normal attributes.
-
# These hash keys are nested attributes implementation details.
-
1
UNASSIGNABLE_KEYS = %w( id _destroy )
-
-
# Assigns the given attributes to the association.
-
#
-
# If an associated record does not yet exist, one will be instantiated. If
-
# an associated record already exists, the method's behavior depends on
-
# the value of the update_only option. If update_only is +false+ and the
-
# given attributes include an <tt>:id</tt> that matches the existing record's
-
# id, then the existing record will be modified. If no <tt>:id</tt> is provided
-
# it will be replaced with a new record. If update_only is +true+ the existing
-
# record will be modified regardless of whether an <tt>:id</tt> is provided.
-
#
-
# If the given attributes include a matching <tt>:id</tt> attribute, or
-
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
-
# then the existing record will be marked for destruction.
-
1
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
-
options = self.nested_attributes_options[association_name]
-
attributes = attributes.with_indifferent_access
-
-
if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
-
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
-
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
-
-
elsif attributes['id'].present?
-
raise_nested_attributes_record_not_found(association_name, attributes['id'])
-
-
elsif !reject_new_record?(association_name, attributes)
-
method = "build_#{association_name}"
-
if respond_to?(method)
-
send(method, attributes.except(*UNASSIGNABLE_KEYS))
-
else
-
raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
-
end
-
end
-
end
-
-
# Assigns the given attributes to the collection association.
-
#
-
# Hashes with an <tt>:id</tt> value matching an existing associated record
-
# will update that record. Hashes without an <tt>:id</tt> value will build
-
# a new record for the association. Hashes with a matching <tt>:id</tt>
-
# value and a <tt>:_destroy</tt> key set to a truthy value will mark the
-
# matched record for destruction.
-
#
-
# For example:
-
#
-
# assign_nested_attributes_for_collection_association(:people, {
-
# '1' => { :id => '1', :name => 'Peter' },
-
# '2' => { :name => 'John' },
-
# '3' => { :id => '2', :_destroy => true }
-
# })
-
#
-
# Will update the name of the Person with ID 1, build a new associated
-
# person with the name 'John', and mark the associated Person with ID 2
-
# for destruction.
-
#
-
# Also accepts an Array of attribute hashes:
-
#
-
# assign_nested_attributes_for_collection_association(:people, [
-
# { :id => '1', :name => 'Peter' },
-
# { :name => 'John' },
-
# { :id => '2', :_destroy => true }
-
# ])
-
1
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
-
options = self.nested_attributes_options[association_name]
-
-
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
-
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
-
end
-
-
if limit = options[:limit]
-
limit = case limit
-
when Symbol
-
send(limit)
-
when Proc
-
limit.call
-
else
-
limit
-
end
-
-
if limit && attributes_collection.size > limit
-
raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
-
end
-
end
-
-
if attributes_collection.is_a? Hash
-
keys = attributes_collection.keys
-
attributes_collection = if keys.include?('id') || keys.include?(:id)
-
[attributes_collection]
-
else
-
attributes_collection.values
-
end
-
end
-
-
association = association(association_name)
-
-
existing_records = if association.loaded?
-
association.target
-
else
-
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
-
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
-
end
-
-
attributes_collection.each do |attributes|
-
attributes = attributes.with_indifferent_access
-
-
if attributes['id'].blank?
-
unless reject_new_record?(association_name, attributes)
-
association.build(attributes.except(*UNASSIGNABLE_KEYS))
-
end
-
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
-
unless association.loaded? || call_reject_if(association_name, attributes)
-
# Make sure we are operating on the actual object which is in the association's
-
# proxy_target array (either by finding it, or adding it if not found)
-
target_record = association.target.detect { |record| record == existing_record }
-
-
if target_record
-
existing_record = target_record
-
else
-
association.add_to_target(existing_record)
-
end
-
end
-
-
if !call_reject_if(association_name, attributes)
-
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
-
end
-
else
-
raise_nested_attributes_record_not_found(association_name, attributes['id'])
-
end
-
end
-
end
-
-
# Updates a record with the +attributes+ or marks it for destruction if
-
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
-
1
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
-
record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
-
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
-
end
-
-
# Determines if a hash contains a truthy _destroy key.
-
1
def has_destroy_flag?(hash)
-
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
-
end
-
-
# Determines if a new record should be build by checking for
-
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
-
# association and evaluates to +true+.
-
1
def reject_new_record?(association_name, attributes)
-
has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
-
end
-
-
1
def call_reject_if(association_name, attributes)
-
return false if has_destroy_flag?(attributes)
-
case callback = self.nested_attributes_options[association_name][:reject_if]
-
when Symbol
-
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
-
when Proc
-
callback.call(attributes)
-
end
-
end
-
-
1
def raise_nested_attributes_record_not_found(association_name, record_id)
-
raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
# = Active Record Observer
-
#
-
# Observer classes respond to life cycle callbacks to implement trigger-like
-
# behavior outside the original class. This is a great way to reduce the
-
# clutter that normally comes when the model class is burdened with
-
# functionality that doesn't pertain to the core responsibility of the
-
# class. Example:
-
#
-
# class CommentObserver < ActiveRecord::Observer
-
# def after_save(comment)
-
# Notifications.comment("admin@do.com", "New comment was posted", comment).deliver
-
# end
-
# end
-
#
-
# This Observer sends an email when a Comment#save is finished.
-
#
-
# class ContactObserver < ActiveRecord::Observer
-
# def after_create(contact)
-
# contact.logger.info('New contact added!')
-
# end
-
#
-
# def after_destroy(contact)
-
# contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
-
# end
-
# end
-
#
-
# This Observer uses logger to log when specific callbacks are triggered.
-
#
-
# == Observing a class that can't be inferred
-
#
-
# Observers will by default be mapped to the class with which they share a name. So CommentObserver will
-
# be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
-
# differently than the class you're interested in observing, you can use the Observer.observe class method which takes
-
# either the concrete class (Product) or a symbol for that class (:product):
-
#
-
# class AuditObserver < ActiveRecord::Observer
-
# observe :account
-
#
-
# def after_update(account)
-
# AuditTrail.new(account, "UPDATED")
-
# end
-
# end
-
#
-
# If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
-
#
-
# class AuditObserver < ActiveRecord::Observer
-
# observe :account, :balance
-
#
-
# def after_update(record)
-
# AuditTrail.new(record, "UPDATED")
-
# end
-
# end
-
#
-
# The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
-
#
-
# == Available callback methods
-
#
-
# The observer can implement callback methods for each of the methods described in the Callbacks module.
-
#
-
# == Storing Observers in Rails
-
#
-
# If you're using Active Record within Rails, observer classes are usually stored in app/models with the
-
# naming convention of app/models/audit_observer.rb.
-
#
-
# == Configuration
-
#
-
# In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration
-
# setting in your <tt>config/application.rb</tt> file.
-
#
-
# config.active_record.observers = :comment_observer, :signup_observer
-
#
-
# Observers will not be invoked unless you define these in your application configuration.
-
#
-
# If you are using Active Record outside Rails, activate the observers explicitly in a configuration or
-
# environment file:
-
#
-
# ActiveRecord::Base.add_observer CommentObserver.instance
-
# ActiveRecord::Base.add_observer SignupObserver.instance
-
#
-
# == Loading
-
#
-
# Observers register themselves in the model class they observe, since it is the class that
-
# notifies them of events when they occur. As a side-effect, when an observer is loaded its
-
# corresponding model class is loaded.
-
#
-
# Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
-
# application initializers. Now observers are loaded after application initializers,
-
# so observed models can make use of extensions.
-
#
-
# If by any chance you are using observed models in the initialization you can still
-
# load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
-
# singletons and that call instantiates and registers them.
-
#
-
1
class Observer < ActiveModel::Observer
-
-
1
protected
-
-
1
def observed_classes
-
5
klasses = super
-
5
klasses + klasses.map { |klass| klass.descendants }.flatten
-
end
-
-
1
def add_observer!(klass)
-
super
-
define_callbacks klass
-
end
-
-
1
def define_callbacks(klass)
-
observer = self
-
observer_name = observer.class.name.underscore.gsub('/', '__')
-
-
ActiveRecord::Callbacks::CALLBACKS.each do |callback|
-
next unless respond_to?(callback)
-
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
-
unless klass.respond_to?(callback_meth)
-
klass.send(:define_method, callback_meth) do |&block|
-
observer.update(callback, self, &block)
-
end
-
klass.send(callback, callback_meth)
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Persistence
-
1
module Persistence
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
-
# The resulting object is returned whether the object was saved successfully to the database or not.
-
#
-
# The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
-
# attributes on the objects that are to be created.
-
#
-
# +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
-
# in the +options+ parameter.
-
#
-
# ==== Examples
-
# # Create a single new object
-
# User.create(first_name: 'Jamie')
-
#
-
# # Create an Array of new objects
-
# User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
-
#
-
# # Create a single object and pass it into a block to set other attributes.
-
# User.create(first_name: 'Jamie') do |u|
-
# u.is_admin = false
-
# end
-
#
-
# # Creating an Array of new objects using a block, where the block is executed for each object:
-
# User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
-
# u.is_admin = false
-
# end
-
1
def create(attributes = nil, &block)
-
if attributes.is_a?(Array)
-
attributes.collect { |attr| create(attr, &block) }
-
else
-
object = new(attributes, &block)
-
object.save
-
object
-
end
-
end
-
end
-
-
# Returns true if this object hasn't been saved yet -- that is, a record
-
# for the object doesn't exist in the data store yet; otherwise, returns false.
-
1
def new_record?
-
@new_record
-
end
-
-
# Returns true if this object has been destroyed, otherwise returns false.
-
1
def destroyed?
-
@destroyed
-
end
-
-
# Returns true if the record is persisted, i.e. it's not a new record and it was
-
# not destroyed, otherwise returns false.
-
1
def persisted?
-
!(new_record? || destroyed?)
-
end
-
-
# Saves the model.
-
#
-
# If the model is new a record gets created in the database, otherwise
-
# the existing record gets updated.
-
#
-
# By default, save always run validations. If any of them fail the action
-
# is cancelled and +save+ returns +false+. However, if you supply
-
# validate: false, validations are bypassed altogether. See
-
# ActiveRecord::Validations for more information.
-
#
-
# There's a series of callbacks associated with +save+. If any of the
-
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
-
# +save+ returns +false+. See ActiveRecord::Callbacks for further
-
# details.
-
1
def save(*)
-
begin
-
create_or_update
-
rescue ActiveRecord::RecordInvalid
-
false
-
end
-
end
-
-
# Saves the model.
-
#
-
# If the model is new a record gets created in the database, otherwise
-
# the existing record gets updated.
-
#
-
# With <tt>save!</tt> validations always run. If any of them fail
-
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
-
# for more information.
-
#
-
# There's a series of callbacks associated with <tt>save!</tt>. If any of
-
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
-
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
-
# ActiveRecord::Callbacks for further details.
-
1
def save!(*)
-
create_or_update || raise(RecordNotSaved)
-
end
-
-
# Deletes the record in the database and freezes this instance to
-
# reflect that no changes should be made (since they can't be
-
# persisted). Returns the frozen instance.
-
#
-
# The row is simply removed with an SQL +DELETE+ statement on the
-
# record's primary key, and no callbacks are executed.
-
#
-
# To enforce the object's +before_destroy+ and +after_destroy+
-
# callbacks, Observer methods, or any <tt>:dependent</tt> association
-
# options, use <tt>#destroy</tt>.
-
1
def delete
-
self.class.delete(id) if persisted?
-
@destroyed = true
-
freeze
-
end
-
-
# Deletes the record in the database and freezes this instance to reflect
-
# that no changes should be made (since they can't be persisted).
-
#
-
# There's a series of callbacks associated with <tt>destroy</tt>. If
-
# the <tt>before_destroy</tt> callback return +false+ the action is cancelled
-
# and <tt>destroy</tt> returns +false+. See
-
# ActiveRecord::Callbacks for further details.
-
1
def destroy
-
raise ReadOnlyRecord if readonly?
-
destroy_associations
-
destroy_row if persisted?
-
@destroyed = true
-
freeze
-
end
-
-
# Deletes the record in the database and freezes this instance to reflect
-
# that no changes should be made (since they can't be persisted).
-
#
-
# There's a series of callbacks associated with <tt>destroy!</tt>. If
-
# the <tt>before_destroy</tt> callback return +false+ the action is cancelled
-
# and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
-
# ActiveRecord::Callbacks for further details.
-
1
def destroy!
-
destroy || raise(ActiveRecord::RecordNotDestroyed)
-
end
-
-
# Returns an instance of the specified +klass+ with the attributes of the
-
# current record. This is mostly useful in relation to single-table
-
# inheritance structures where you want a subclass to appear as the
-
# superclass. This can be used along with record identification in
-
# Action Pack to allow, say, <tt>Client < Company</tt> to do something
-
# like render <tt>partial: @client.becomes(Company)</tt> to render that
-
# instance using the companies/company partial instead of clients/client.
-
#
-
# Note: The new instance will share a link to the same attributes as the original class.
-
# So any change to the attributes in either instance will affect the other.
-
1
def becomes(klass)
-
became = klass.new
-
became.instance_variable_set("@attributes", @attributes)
-
became.instance_variable_set("@attributes_cache", @attributes_cache)
-
became.instance_variable_set("@new_record", new_record?)
-
became.instance_variable_set("@destroyed", destroyed?)
-
became.instance_variable_set("@errors", errors)
-
became.public_send("#{klass.inheritance_column}=", klass.name) unless self.class.descends_from_active_record?
-
became
-
end
-
-
# Updates a single attribute and saves the record.
-
# This is especially useful for boolean flags on existing records. Also note that
-
#
-
# * Validation is skipped.
-
# * Callbacks are invoked.
-
# * updated_at/updated_on column is updated if that column is available.
-
# * Updates all the attributes that are dirty in this object.
-
#
-
1
def update_attribute(name, value)
-
name = name.to_s
-
verify_readonly_attribute(name)
-
send("#{name}=", value)
-
save(:validate => false)
-
end
-
-
# Updates the attributes of the model from the passed-in hash and saves the
-
# record, all wrapped in a transaction. If the object is invalid, the saving
-
# will fail and false will be returned.
-
1
def update_attributes(attributes)
-
# The following transaction covers any possible database side-effects of the
-
# attributes assignment. For example, setting the IDs of a child collection.
-
with_transaction_returning_status do
-
assign_attributes(attributes)
-
save
-
end
-
end
-
-
# Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
-
# of +save+, so an exception is raised if the record is invalid.
-
1
def update_attributes!(attributes)
-
# The following transaction covers any possible database side-effects of the
-
# attributes assignment. For example, setting the IDs of a child collection.
-
with_transaction_returning_status do
-
assign_attributes(attributes)
-
save!
-
end
-
end
-
-
# Updates a single attribute of an object, without having to explicitly call save on that object.
-
#
-
# * Validation is skipped.
-
# * Callbacks are skipped.
-
# * updated_at/updated_on column is not updated if that column is available.
-
#
-
# Raises an +ActiveRecordError+ when called on new objects, or when the +name+
-
# attribute is marked as readonly.
-
1
def update_column(name, value)
-
update_columns(name => value)
-
end
-
-
# Updates the attributes from the passed-in hash, without having to explicitly call save on that object.
-
#
-
# * Validation is skipped.
-
# * Callbacks are skipped.
-
# * updated_at/updated_on column is not updated if that column is available.
-
#
-
# Raises an +ActiveRecordError+ when called on new objects, or when at least
-
# one of the attributes is marked as readonly.
-
1
def update_columns(attributes)
-
raise ActiveRecordError, "can not update on a new record object" unless persisted?
-
-
attributes.each_key do |key|
-
verify_readonly_attribute(key.to_s)
-
end
-
-
updated_count = self.class.where(self.class.primary_key => id).update_all(attributes)
-
-
attributes.each do |k,v|
-
raw_write_attribute(k,v)
-
end
-
-
updated_count == 1
-
end
-
-
# Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
-
# The increment is performed directly on the underlying attribute, no setter is invoked.
-
# Only makes sense for number-based attributes. Returns +self+.
-
1
def increment(attribute, by = 1)
-
self[attribute] ||= 0
-
self[attribute] += by
-
self
-
end
-
-
# Wrapper around +increment+ that saves the record. This method differs from
-
# its non-bang version in that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
1
def increment!(attribute, by = 1)
-
increment(attribute, by).update_attribute(attribute, self[attribute])
-
end
-
-
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
-
# The decrement is performed directly on the underlying attribute, no setter is invoked.
-
# Only makes sense for number-based attributes. Returns +self+.
-
1
def decrement(attribute, by = 1)
-
self[attribute] ||= 0
-
self[attribute] -= by
-
self
-
end
-
-
# Wrapper around +decrement+ that saves the record. This method differs from
-
# its non-bang version in that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
1
def decrement!(attribute, by = 1)
-
decrement(attribute, by).update_attribute(attribute, self[attribute])
-
end
-
-
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
-
# if the predicate returns +true+ the attribute will become +false+. This
-
# method toggles directly the underlying value without calling any setter.
-
# Returns +self+.
-
1
def toggle(attribute)
-
self[attribute] = !send("#{attribute}?")
-
self
-
end
-
-
# Wrapper around +toggle+ that saves the record. This method differs from
-
# its non-bang version in that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
1
def toggle!(attribute)
-
toggle(attribute).update_attribute(attribute, self[attribute])
-
end
-
-
# Reloads the attributes of this object from the database.
-
# The optional options argument is passed to find when reloading so you
-
# may do e.g. record.reload(lock: true) to reload the same record with
-
# an exclusive row lock.
-
1
def reload(options = nil)
-
clear_aggregation_cache
-
clear_association_cache
-
-
fresh_object =
-
if options && options[:lock]
-
self.class.unscoped { self.class.lock.find(id) }
-
else
-
self.class.unscoped { self.class.find(id) }
-
end
-
-
@attributes.update(fresh_object.instance_variable_get('@attributes'))
-
@columns_hash = fresh_object.instance_variable_get('@columns_hash')
-
-
@attributes_cache = {}
-
self
-
end
-
-
# Saves the record with the updated_at/on attributes set to the current time.
-
# Please note that no validation is performed and no callbacks are executed.
-
# If an attribute name is passed, that attribute is updated along with
-
# updated_at/on attributes.
-
#
-
# product.touch # updates updated_at/on
-
# product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
-
#
-
# If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
-
#
-
# class Brake < ActiveRecord::Base
-
# belongs_to :car, touch: true
-
# end
-
#
-
# class Car < ActiveRecord::Base
-
# belongs_to :corporation, touch: true
-
# end
-
#
-
# # triggers @brake.car.touch and @brake.car.corporation.touch
-
# @brake.touch
-
1
def touch(name = nil)
-
attributes = timestamp_attributes_for_update_in_model
-
attributes << name if name
-
-
unless attributes.empty?
-
current_time = current_time_from_proper_timezone
-
changes = {}
-
-
attributes.each do |column|
-
column = column.to_s
-
changes[column] = write_attribute(column, current_time)
-
end
-
-
changes[self.class.locking_column] = increment_lock if locking_enabled?
-
-
@changed_attributes.except!(*changes.keys)
-
primary_key = self.class.primary_key
-
self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
-
end
-
end
-
-
1
private
-
-
# A hook to be overridden by association modules.
-
1
def destroy_associations
-
end
-
-
1
def destroy_row
-
relation_for_destroy.delete_all
-
end
-
-
1
def relation_for_destroy
-
pk = self.class.primary_key
-
column = self.class.columns_hash[pk]
-
substitute = connection.substitute_at(column, 0)
-
-
relation = self.class.unscoped.where(
-
self.class.arel_table[pk].eq(substitute))
-
-
relation.bind_values = [[column, id]]
-
relation
-
end
-
-
1
def create_or_update
-
raise ReadOnlyRecord if readonly?
-
result = new_record? ? create : update
-
result != false
-
end
-
-
# Updates the associated record with values matching those of the instance attributes.
-
# Returns the number of affected rows.
-
1
def update(attribute_names = @attributes.keys)
-
attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
-
return 0 if attributes_with_values.empty?
-
klass = self.class
-
stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
-
klass.connection.update stmt
-
end
-
-
# Creates a record with values matching those of the instance attributes
-
# and returns its id.
-
1
def create(attribute_names = @attributes.keys)
-
attributes_values = arel_attributes_with_values_for_create(attribute_names)
-
-
new_id = self.class.unscoped.insert attributes_values
-
self.id ||= new_id if self.class.primary_key
-
-
@new_record = false
-
id
-
end
-
-
1
def verify_readonly_attribute(name)
-
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
# = Active Record Query Cache
-
1
class QueryCache
-
1
module ClassMethods
-
# Enable the query cache within the block if Active Record is configured.
-
1
def cache(&block)
-
if ActiveRecord::Base.connected?
-
connection.cache(&block)
-
else
-
yield
-
end
-
end
-
-
# Disable the query cache within the block if Active Record is configured.
-
1
def uncached(&block)
-
if ActiveRecord::Base.connected?
-
connection.uncached(&block)
-
else
-
yield
-
end
-
end
-
end
-
-
1
def initialize(app)
-
@app = app
-
end
-
-
1
def call(env)
-
enabled = ActiveRecord::Base.connection.query_cache_enabled
-
connection_id = ActiveRecord::Base.connection_id
-
ActiveRecord::Base.connection.enable_query_cache!
-
-
response = @app.call(env)
-
response[2] = Rack::BodyProxy.new(response[2]) do
-
restore_query_cache_settings(connection_id, enabled)
-
end
-
-
response
-
rescue Exception => e
-
restore_query_cache_settings(connection_id, enabled)
-
raise e
-
end
-
-
1
private
-
-
1
def restore_query_cache_settings(connection_id, enabled)
-
ActiveRecord::Base.connection_id = connection_id
-
ActiveRecord::Base.connection.clear_query_cache
-
ActiveRecord::Base.connection.disable_query_cache! unless enabled
-
end
-
-
end
-
end
-
-
1
module ActiveRecord
-
1
module Querying
-
1
delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :to => :all
-
1
delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :all
-
1
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :to => :all
-
1
delegate :find_by, :find_by!, :to => :all
-
1
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :all
-
1
delegate :find_each, :find_in_batches, :to => :all
-
1
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
-
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
-
:having, :create_with, :uniq, :references, :none, :to => :all
-
1
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
-
-
# Executes a custom SQL query against your database and returns all the results. The results will
-
# be returned as an array with columns requested encapsulated as attributes of the model you call
-
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
-
# a Product object with the attributes you specified in the SQL query.
-
#
-
# If you call a complicated SQL query which spans multiple tables the columns specified by the
-
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
-
# table.
-
#
-
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
-
# no database agnostic conversions performed. This should be a last resort because using, for example,
-
# MySQL specific terms will lock you to using that particular database engine or require you to
-
# change your call if you switch engines.
-
#
-
# ==== Examples
-
# # A simple SQL query spanning multiple tables
-
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
-
# > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
-
#
-
# # You can use the same string replacement techniques as you can with ActiveRecord#find
-
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
-
# > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
-
1
def find_by_sql(sql, binds = [])
-
logging_query_plan do
-
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
-
column_types = {}
-
-
if result_set.respond_to? :column_types
-
column_types = result_set.column_types
-
else
-
ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
-
end
-
-
result_set.map { |record| instantiate(record, column_types) }
-
end
-
end
-
-
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
-
# The use of this method should be restricted to complicated SQL queries that can't be executed
-
# using the ActiveRecord::Calculations class methods. Look into those before using this.
-
#
-
# ==== Parameters
-
#
-
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
-
#
-
# ==== Examples
-
#
-
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
-
1
def count_by_sql(sql)
-
logging_query_plan do
-
sql = sanitize_conditions(sql)
-
connection.select_value(sql, "#{name} Count").to_i
-
end
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module ReadonlyAttributes
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :_attr_readonly, instance_accessor: false
-
1
self._attr_readonly = []
-
end
-
-
1
module ClassMethods
-
# Attributes listed as readonly will be used to create a new record but update operations will
-
# ignore these fields.
-
1
def attr_readonly(*attributes)
-
self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
-
end
-
-
# Returns an array of all the attributes that have been specified as readonly.
-
1
def readonly_attributes
-
self._attr_readonly
-
end
-
end
-
-
1
def _attr_readonly
-
message = "Instance level _attr_readonly method is deprecated, please use class level method."
-
ActiveSupport::Deprecation.warn message
-
defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
# = Active Record Reflection
-
1
module Reflection # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :reflections
-
1
self.reflections = {}
-
end
-
-
# Reflection enables to interrogate Active Record classes and objects
-
# about their associations and aggregations. This information can,
-
# for example, be used in a form builder that takes an Active Record object
-
# and creates input fields for all of the attributes depending on their type
-
# and displays the associations to other objects.
-
#
-
# MacroReflection class has info for AggregateReflection and AssociationReflection
-
# classes.
-
1
module ClassMethods
-
1
def create_reflection(macro, name, scope, options, active_record)
-
9
case macro
-
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
-
9
klass = options[:through] ? ThroughReflection : AssociationReflection
-
9
reflection = klass.new(macro, name, scope, options, active_record)
-
when :composed_of
-
reflection = AggregateReflection.new(macro, name, scope, options, active_record)
-
end
-
-
9
self.reflections = self.reflections.merge(name => reflection)
-
9
reflection
-
end
-
-
# Returns an array of AggregateReflection objects for all the aggregations in the class.
-
1
def reflect_on_all_aggregations
-
reflections.values.grep(AggregateReflection)
-
end
-
-
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
-
#
-
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
-
#
-
1
def reflect_on_aggregation(aggregation)
-
reflection = reflections[aggregation]
-
reflection if reflection.is_a?(AggregateReflection)
-
end
-
-
# Returns an array of AssociationReflection objects for all the
-
# associations in the class. If you only want to reflect on a certain
-
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
-
# <tt>:belongs_to</tt>) as the first parameter.
-
#
-
# Example:
-
#
-
# Account.reflect_on_all_associations # returns an array of all associations
-
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
-
#
-
1
def reflect_on_all_associations(macro = nil)
-
association_reflections = reflections.values.grep(AssociationReflection)
-
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
-
end
-
-
# Returns the AssociationReflection object for the +association+ (use the symbol).
-
#
-
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
-
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
-
#
-
1
def reflect_on_association(association)
-
reflection = reflections[association]
-
reflection if reflection.is_a?(AssociationReflection)
-
end
-
-
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
-
1
def reflect_on_all_autosave_associations
-
reflections.values.select { |reflection| reflection.options[:autosave] }
-
end
-
end
-
-
# Abstract base class for AggregateReflection and AssociationReflection. Objects of
-
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
-
1
class MacroReflection
-
# Returns the name of the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
-
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
-
1
attr_reader :name
-
-
# Returns the macro type.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:composed_of</tt>
-
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
-
1
attr_reader :macro
-
-
1
attr_reader :scope
-
-
# Returns the hash of options used for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
-
# <tt>has_many :clients</tt> returns +{}+
-
1
attr_reader :options
-
-
1
attr_reader :active_record
-
-
1
attr_reader :plural_name # :nodoc:
-
-
1
def initialize(macro, name, scope, options, active_record)
-
9
@macro = macro
-
9
@name = name
-
9
@scope = scope
-
9
@options = options
-
9
@active_record = active_record
-
9
@plural_name = active_record.pluralize_table_names ?
-
name.to_s.pluralize : name.to_s
-
end
-
-
# Returns the class for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
-
# <tt>has_many :clients</tt> returns the Client class
-
1
def klass
-
@klass ||= class_name.constantize
-
end
-
-
# Returns the class name for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
-
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
-
1
def class_name
-
@class_name ||= (options[:class_name] || derive_class_name).to_s
-
end
-
-
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
-
# and +other_aggregation+ has an options hash assigned to it.
-
1
def ==(other_aggregation)
-
super ||
-
other_aggregation.kind_of?(self.class) &&
-
name == other_aggregation.name &&
-
other_aggregation.options &&
-
active_record == other_aggregation.active_record
-
end
-
-
1
private
-
1
def derive_class_name
-
name.to_s.camelize
-
end
-
end
-
-
-
# Holds all the meta-data about an aggregation as it was specified in the
-
# Active Record class.
-
1
class AggregateReflection < MacroReflection #:nodoc:
-
1
def mapping
-
mapping = options[:mapping] || [name, name]
-
mapping.first.is_a?(Array) ? mapping : [mapping]
-
end
-
end
-
-
# Holds all the meta-data about an association as it was specified in the
-
# Active Record class.
-
1
class AssociationReflection < MacroReflection #:nodoc:
-
# Returns the target association's class.
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :books
-
# end
-
#
-
# Author.reflect_on_association(:books).klass
-
# # => Book
-
#
-
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
-
# a new association object. Use +build_association+ or +create_association+
-
# instead. This allows plugins to hook into association object creation.
-
1
def klass
-
@klass ||= active_record.send(:compute_type, class_name)
-
end
-
-
1
def initialize(*args)
-
9
super
-
9
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
-
end
-
-
# Returns a new, unsaved instance of the associated class. +options+ will
-
# be passed to the class's constructor.
-
1
def build_association(attributes, &block)
-
klass.new(attributes, &block)
-
end
-
-
1
def table_name
-
@table_name ||= klass.table_name
-
end
-
-
1
def quoted_table_name
-
@quoted_table_name ||= klass.quoted_table_name
-
end
-
-
1
def join_table
-
@join_table ||= options[:join_table] || derive_join_table
-
end
-
-
1
def foreign_key
-
@foreign_key ||= options[:foreign_key] || derive_foreign_key
-
end
-
-
1
def foreign_type
-
@foreign_type ||= options[:foreign_type] || "#{name}_type"
-
end
-
-
1
def type
-
@type ||= options[:as] && "#{options[:as]}_type"
-
end
-
-
1
def primary_key_column
-
@primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
-
end
-
-
1
def association_foreign_key
-
@association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
-
end
-
-
# klass option is necessary to support loading polymorphic associations
-
1
def association_primary_key(klass = nil)
-
options[:primary_key] || primary_key(klass || self.klass)
-
end
-
-
1
def active_record_primary_key
-
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
-
end
-
-
1
def counter_cache_column
-
if options[:counter_cache] == true
-
"#{active_record.name.demodulize.underscore.pluralize}_count"
-
elsif options[:counter_cache]
-
options[:counter_cache].to_s
-
end
-
end
-
-
1
def columns(tbl_name)
-
@columns ||= klass.connection.columns(tbl_name)
-
end
-
-
1
def reset_column_information
-
@columns = nil
-
end
-
-
1
def check_validity!
-
check_validity_of_inverse!
-
-
if has_and_belongs_to_many? && association_foreign_key == foreign_key
-
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
-
end
-
end
-
-
1
def check_validity_of_inverse!
-
unless options[:polymorphic]
-
if has_inverse? && inverse_of.nil?
-
raise InverseOfAssociationNotFoundError.new(self)
-
end
-
end
-
end
-
-
1
def through_reflection
-
nil
-
end
-
-
1
def source_reflection
-
nil
-
end
-
-
# A chain of reflections from this one back to the owner. For more see the explanation in
-
# ThroughReflection.
-
1
def chain
-
[self]
-
end
-
-
1
def nested?
-
false
-
end
-
-
# An array of arrays of scopes. Each item in the outside array corresponds to a reflection
-
# in the #chain.
-
1
def scope_chain
-
scope ? [[scope]] : [[]]
-
end
-
-
1
alias :source_macro :macro
-
-
1
def has_inverse?
-
@options[:inverse_of]
-
end
-
-
1
def inverse_of
-
if has_inverse?
-
@inverse_of ||= klass.reflect_on_association(options[:inverse_of])
-
end
-
end
-
-
1
def polymorphic_inverse_of(associated_class)
-
if has_inverse?
-
if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
-
inverse_relationship
-
else
-
raise InverseOfAssociationNotFoundError.new(self, associated_class)
-
end
-
end
-
end
-
-
# Returns whether or not this association reflection is for a collection
-
# association. Returns +true+ if the +macro+ is either +has_many+ or
-
# +has_and_belongs_to_many+, +false+ otherwise.
-
1
def collection?
-
9
@collection
-
end
-
-
# Returns whether or not the association should be validated as part of
-
# the parent's validation.
-
#
-
# Unless you explicitly disable validation with
-
# <tt>validate: false</tt>, validation will take place when:
-
#
-
# * you explicitly enable validation; <tt>validate: true</tt>
-
# * you use autosave; <tt>autosave: true</tt>
-
# * the association is a +has_many+ association
-
1
def validate?
-
9
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
-
end
-
-
# Returns +true+ if +self+ is a +belongs_to+ reflection.
-
1
def belongs_to?
-
macro == :belongs_to
-
end
-
-
1
def has_and_belongs_to_many?
-
macro == :has_and_belongs_to_many
-
end
-
-
1
def association_class
-
case macro
-
when :belongs_to
-
if options[:polymorphic]
-
Associations::BelongsToPolymorphicAssociation
-
else
-
Associations::BelongsToAssociation
-
end
-
when :has_and_belongs_to_many
-
Associations::HasAndBelongsToManyAssociation
-
when :has_many
-
if options[:through]
-
Associations::HasManyThroughAssociation
-
else
-
Associations::HasManyAssociation
-
end
-
when :has_one
-
if options[:through]
-
Associations::HasOneThroughAssociation
-
else
-
Associations::HasOneAssociation
-
end
-
end
-
end
-
-
1
def polymorphic?
-
options.key? :polymorphic
-
end
-
-
1
private
-
1
def derive_class_name
-
class_name = name.to_s.camelize
-
class_name = class_name.singularize if collection?
-
class_name
-
end
-
-
1
def derive_foreign_key
-
if belongs_to?
-
"#{name}_id"
-
elsif options[:as]
-
"#{options[:as]}_id"
-
else
-
active_record.name.foreign_key
-
end
-
end
-
-
1
def derive_join_table
-
[active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
-
end
-
-
1
def primary_key(klass)
-
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
-
end
-
end
-
-
# Holds all the meta-data about a :through association as it was specified
-
# in the Active Record class.
-
1
class ThroughReflection < AssociationReflection #:nodoc:
-
1
delegate :foreign_key, :foreign_type, :association_foreign_key,
-
:active_record_primary_key, :type, :to => :source_reflection
-
-
# Gets the source of the through reflection. It checks both a singularized
-
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
1
def source_reflection
-
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
-
end
-
-
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
-
# of a HasManyThrough or HasOneThrough association.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# taggings_reflection = tags_reflection.through_reflection
-
#
-
1
def through_reflection
-
@through_reflection ||= active_record.reflect_on_association(options[:through])
-
end
-
-
# Returns an array of reflections which are involved in this association. Each item in the
-
# array corresponds to a table which will be part of the query for this association.
-
#
-
# The chain is built by recursively calling #chain on the source reflection and the through
-
# reflection. The base case for the recursion is a normal association, which just returns
-
# [self] as its #chain.
-
1
def chain
-
@chain ||= begin
-
chain = source_reflection.chain + through_reflection.chain
-
chain[0] = self # Use self so we don't lose the information from :source_type
-
chain
-
end
-
end
-
-
# Consider the following example:
-
#
-
# class Person
-
# has_many :articles
-
# has_many :comment_tags, through: :articles
-
# end
-
#
-
# class Article
-
# has_many :comments
-
# has_many :comment_tags, through: :comments, source: :tags
-
# end
-
#
-
# class Comment
-
# has_many :tags
-
# end
-
#
-
# There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
-
# but only Comment.tags will be represented in the #chain. So this method creates an array
-
# of scopes corresponding to the chain.
-
1
def scope_chain
-
@scope_chain ||= begin
-
scope_chain = source_reflection.scope_chain.map(&:dup)
-
-
# Add to it the scope from this reflection (if any)
-
scope_chain.first << scope if scope
-
-
through_scope_chain = through_reflection.scope_chain
-
-
if options[:source_type]
-
through_scope_chain.first <<
-
through_reflection.klass.where(foreign_type => options[:source_type])
-
end
-
-
# Recursively fill out the rest of the array from the through reflection
-
scope_chain + through_scope_chain
-
end
-
end
-
-
# The macro used by the source association
-
1
def source_macro
-
source_reflection.source_macro
-
end
-
-
# A through association is nested if there would be more than one join table
-
1
def nested?
-
chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
-
end
-
-
# We want to use the klass from this reflection, rather than just delegate straight to
-
# the source_reflection, because the source_reflection may be polymorphic. We still
-
# need to respect the source_reflection's :primary_key option, though.
-
1
def association_primary_key(klass = nil)
-
# Get the "actual" source reflection if the immediate source reflection has a
-
# source reflection itself
-
source_reflection = self.source_reflection
-
while source_reflection.source_reflection
-
source_reflection = source_reflection.source_reflection
-
end
-
-
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
-
end
-
-
# Gets an array of possible <tt>:through</tt> source reflection names:
-
#
-
# [:singularized, :pluralized]
-
#
-
1
def source_reflection_names
-
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
-
end
-
-
1
def source_options
-
source_reflection.options
-
end
-
-
1
def through_options
-
through_reflection.options
-
end
-
-
1
def check_validity!
-
if through_reflection.nil?
-
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
-
end
-
-
if through_reflection.options[:polymorphic]
-
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
-
end
-
-
if source_reflection.nil?
-
raise HasManyThroughSourceAssociationNotFoundError.new(self)
-
end
-
-
if options[:source_type] && source_reflection.options[:polymorphic].nil?
-
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
-
end
-
-
if source_reflection.options[:polymorphic] && options[:source_type].nil?
-
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
-
end
-
-
if macro == :has_one && through_reflection.collection?
-
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
-
end
-
-
check_validity_of_inverse!
-
end
-
-
1
private
-
1
def derive_class_name
-
# get the class_name of the belongs_to association of the through reflection
-
options[:source_type] || source_reflection.class_name
-
end
-
end
-
end
-
end
-
# -*- coding: utf-8 -*-
-
-
1
module ActiveRecord
-
# = Active Record Relation
-
1
class Relation
-
1
JoinOperation = Struct.new(:relation, :join_class, :on)
-
-
1
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
-
:order, :joins, :where, :having, :bind, :references,
-
:extending]
-
-
1
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
-
:reverse_order, :uniq, :create_with]
-
-
1
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
-
-
1
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
-
-
1
attr_reader :table, :klass, :loaded
-
1
attr_accessor :default_scoped
-
1
alias :model :klass
-
1
alias :loaded? :loaded
-
1
alias :default_scoped? :default_scoped
-
-
1
def initialize(klass, table, values = {})
-
1
@klass = klass
-
1
@table = table
-
1
@values = values
-
1
@implicit_readonly = nil
-
1
@loaded = false
-
1
@default_scoped = false
-
end
-
-
1
def insert(values)
-
primary_key_value = nil
-
-
if primary_key && Hash === values
-
primary_key_value = values[values.keys.find { |k|
-
k.name == primary_key
-
}]
-
-
if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
-
primary_key_value = connection.next_sequence_value(klass.sequence_name)
-
values[klass.arel_table[klass.primary_key]] = primary_key_value
-
end
-
end
-
-
im = arel.create_insert
-
im.into @table
-
-
conn = @klass.connection
-
-
substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
-
binds = substitutes.map do |arel_attr, value|
-
[@klass.columns_hash[arel_attr.name], value]
-
end
-
-
substitutes.each_with_index do |tuple, i|
-
tuple[1] = conn.substitute_at(binds[i][0], i)
-
end
-
-
if values.empty? # empty insert
-
im.values = Arel.sql(connection.empty_insert_statement_value)
-
else
-
im.insert substitutes
-
end
-
-
conn.insert(
-
im,
-
'SQL',
-
primary_key,
-
primary_key_value,
-
nil,
-
binds)
-
end
-
-
# Initializes new record from relation while maintaining the current
-
# scope.
-
#
-
# Expects arguments in the same format as +Base.new+.
-
#
-
# users = User.where(name: 'DHH')
-
# user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
-
#
-
# You can also pass a block to new with the new record as argument:
-
#
-
# user = users.new { |user| user.name = 'Oscar' }
-
# user.name # => Oscar
-
1
def new(*args, &block)
-
scoping { @klass.new(*args, &block) }
-
end
-
-
1
def initialize_copy(other)
-
# This method is a hot spot, so for now, use Hash[] to dup the hash.
-
# https://bugs.ruby-lang.org/issues/7166
-
@values = Hash[@values]
-
@values[:bind] = @values[:bind].dup if @values.key? :bind
-
reset
-
end
-
-
1
alias build new
-
-
# Tries to create a new record with the same scoped attributes
-
# defined in the relation. Returns the initialized object if validation fails.
-
#
-
# Expects arguments in the same format as +Base.create+.
-
#
-
# ==== Examples
-
# users = User.where(name: 'Oscar')
-
# users.create # #<User id: 3, name: "oscar", ...>
-
#
-
# users.create(name: 'fxn')
-
# users.create # #<User id: 4, name: "fxn", ...>
-
#
-
# users.create { |user| user.name = 'tenderlove' }
-
# # #<User id: 5, name: "tenderlove", ...>
-
#
-
# users.create(name: nil) # validation on name
-
# # #<User id: nil, name: nil, ...>
-
1
def create(*args, &block)
-
scoping { @klass.create(*args, &block) }
-
end
-
-
# Similar to #create, but calls +create!+ on the base class. Raises
-
# an exception if a validation error occurs.
-
#
-
# Expects arguments in the same format as <tt>Base.create!</tt>.
-
1
def create!(*args, &block)
-
scoping { @klass.create!(*args, &block) }
-
end
-
-
1
def first_or_create(attributes = nil, &block) # :nodoc:
-
first || create(attributes, &block)
-
end
-
-
1
def first_or_create!(attributes = nil, &block) # :nodoc:
-
first || create!(attributes, &block)
-
end
-
-
1
def first_or_initialize(attributes = nil, &block) # :nodoc:
-
first || new(attributes, &block)
-
end
-
-
# Finds the first record with the given attributes, or creates a record with the attributes
-
# if one is not found.
-
#
-
# ==== Examples
-
# # Find the first user named Penélope or create a new one.
-
# User.find_or_create_by(first_name: 'Penélope')
-
# # => <User id: 1, first_name: 'Penélope', last_name: nil>
-
#
-
# # Find the first user named Penélope or create a new one.
-
# # We already have one so the existing record will be returned.
-
# User.find_or_create_by(first_name: 'Penélope')
-
# # => <User id: 1, first_name: 'Penélope', last_name: nil>
-
#
-
# # Find the first user named Scarlett or create a new one with a particular last name.
-
# User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
-
# # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
-
#
-
# # Find the first user named Scarlett or create a new one with a different last name.
-
# # We already have one so the existing record will be returned.
-
# User.find_or_create_by(first_name: 'Scarlett') do |user|
-
# user.last_name = "O'Hara"
-
# end
-
# # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
-
1
def find_or_create_by(attributes, &block)
-
find_by(attributes) || create(attributes, &block)
-
end
-
-
# Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
-
1
def find_or_create_by!(attributes, &block)
-
find_by(attributes) || create!(attributes, &block)
-
end
-
-
# Like <tt>find_or_create_by</tt>, but calls <tt>new</tt> instead of <tt>create</tt>.
-
1
def find_or_initialize_by(attributes, &block)
-
find_by(attributes) || new(attributes, &block)
-
end
-
-
# Runs EXPLAIN on the query or queries triggered by this relation and
-
# returns the result as a string. The string is formatted imitating the
-
# ones printed by the database shell.
-
#
-
# Note that this method actually runs the queries, since the results of some
-
# are needed by the next ones when eager loading is going on.
-
#
-
# Please see further details in the
-
# {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
-
1
def explain
-
_, queries = collecting_queries_for_explain { exec_queries }
-
exec_explain(queries)
-
end
-
-
# Converts relation objects to Array.
-
1
def to_a
-
load
-
@records
-
end
-
-
1
def as_json(options = nil) #:nodoc:
-
to_a.as_json(options)
-
end
-
-
# Returns size of the records.
-
1
def size
-
loaded? ? @records.length : count
-
end
-
-
# Returns true if there are no records.
-
1
def empty?
-
return @records.empty? if loaded?
-
-
c = count
-
c.respond_to?(:zero?) ? c.zero? : c.empty?
-
end
-
-
# Returns true if there are any records.
-
1
def any?
-
if block_given?
-
to_a.any? { |*block_args| yield(*block_args) }
-
else
-
!empty?
-
end
-
end
-
-
# Returns true if there is more than one record.
-
1
def many?
-
if block_given?
-
to_a.many? { |*block_args| yield(*block_args) }
-
else
-
limit_value ? to_a.many? : size > 1
-
end
-
end
-
-
# Scope all queries to the current scope.
-
#
-
# Comment.where(post_id: 1).scoping do
-
# Comment.first # SELECT * FROM comments WHERE post_id = 1
-
# end
-
#
-
# Please check unscoped if you want to remove all previous scopes (including
-
# the default_scope) during the execution of a block.
-
1
def scoping
-
previous, klass.current_scope = klass.current_scope, self
-
yield
-
ensure
-
klass.current_scope = previous
-
end
-
-
# Updates all records with details given if they match a set of conditions supplied, limits and order can
-
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
-
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks
-
# or validations.
-
#
-
# ==== Parameters
-
#
-
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
-
#
-
# ==== Examples
-
#
-
# # Update all customers with the given attributes
-
# Customer.update_all wants_email: true
-
#
-
# # Update all books with 'Rails' in their title
-
# Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
-
#
-
# # Update all books that match conditions, but limit it to 5 ordered by date
-
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
-
1
def update_all(updates)
-
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
-
-
stmt = Arel::UpdateManager.new(arel.engine)
-
-
stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
-
stmt.table(table)
-
stmt.key = table[primary_key]
-
-
if joins_values.any?
-
@klass.connection.join_to_update(stmt, arel)
-
else
-
stmt.take(arel.limit)
-
stmt.order(*arel.orders)
-
stmt.wheres = arel.constraints
-
end
-
-
@klass.connection.update stmt, 'SQL', bind_values
-
end
-
-
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
-
# The resulting object is returned whether the object was saved successfully to the database or not.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - This should be the id or an array of ids to be updated.
-
# * +attributes+ - This should be a hash of attributes or an array of hashes.
-
#
-
# ==== Examples
-
#
-
# # Updates one record
-
# Person.update(15, user_name: 'Samuel', group: 'expert')
-
#
-
# # Updates multiple records
-
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
-
# Person.update(people.keys, people.values)
-
1
def update(id, attributes)
-
if id.is_a?(Array)
-
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
-
else
-
object = find(id)
-
object.update_attributes(attributes)
-
object
-
end
-
end
-
-
# Destroys the records matching +conditions+ by instantiating each
-
# record and calling its +destroy+ method. Each object's callbacks are
-
# executed (including <tt>:dependent</tt> association options and
-
# +before_destroy+/+after_destroy+ Observer methods). Returns the
-
# collection of objects that were destroyed; each will be frozen, to
-
# reflect that no changes should be made (since they can't be
-
# persisted).
-
#
-
# Note: Instantiation, callback execution, and deletion of each
-
# record can be time consuming when you're removing many records at
-
# once. It generates at least one SQL +DELETE+ query per record (or
-
# possibly more, to enforce your callbacks). If you want to delete many
-
# rows quickly, without concern for their associations or callbacks, use
-
# +delete_all+ instead.
-
#
-
# ==== Parameters
-
#
-
# * +conditions+ - A string, array, or hash that specifies which records
-
# to destroy. If omitted, all records are destroyed. See the
-
# Conditions section in the introduction to ActiveRecord::Base for
-
# more information.
-
#
-
# ==== Examples
-
#
-
# Person.destroy_all("last_login < '2004-04-04'")
-
# Person.destroy_all(status: "inactive")
-
# Person.where(age: 0..18).destroy_all
-
1
def destroy_all(conditions = nil)
-
if conditions
-
where(conditions).destroy_all
-
else
-
to_a.each {|object| object.destroy }.tap { reset }
-
end
-
end
-
-
# Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
-
# therefore all callbacks and filters are fired off before the object is deleted. This method is
-
# less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
-
#
-
# This essentially finds the object (or multiple objects) with the given id, creates a new object
-
# from the attributes, and then calls destroy on it.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - Can be either an Integer or an Array of Integers.
-
#
-
# ==== Examples
-
#
-
# # Destroy a single object
-
# Todo.destroy(1)
-
#
-
# # Destroy multiple objects
-
# todos = [1,2,3]
-
# Todo.destroy(todos)
-
1
def destroy(id)
-
if id.is_a?(Array)
-
id.map { |one_id| destroy(one_id) }
-
else
-
find(id).destroy
-
end
-
end
-
-
# Deletes the records matching +conditions+ without instantiating the records
-
# first, and hence not calling the +destroy+ method nor invoking callbacks. This
-
# is a single SQL DELETE statement that goes straight to the database, much more
-
# efficient than +destroy_all+. Be careful with relations though, in particular
-
# <tt>:dependent</tt> rules defined on associations are not honored. Returns the
-
# number of rows affected.
-
#
-
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
-
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
-
# Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
-
#
-
# Both calls delete the affected posts all at once with a single DELETE statement.
-
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
-
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
-
#
-
# If a limit scope is supplied, +delete_all+ raises an ActiveRecord error:
-
#
-
# Post.limit(100).delete_all
-
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit scope
-
1
def delete_all(conditions = nil)
-
raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
-
-
if conditions
-
where(conditions).delete_all
-
else
-
stmt = Arel::DeleteManager.new(arel.engine)
-
stmt.from(table)
-
-
if joins_values.any?
-
@klass.connection.join_to_delete(stmt, arel, table[primary_key])
-
else
-
stmt.wheres = arel.constraints
-
end
-
-
affected = @klass.connection.delete(stmt, 'SQL', bind_values)
-
-
reset
-
affected
-
end
-
end
-
-
# Deletes the row with a primary key matching the +id+ argument, using a
-
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
-
# Record objects are not instantiated, so the object's callbacks are not
-
# executed, including any <tt>:dependent</tt> association options or
-
# Observer methods.
-
#
-
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
-
#
-
# Note: Although it is often much faster than the alternative,
-
# <tt>#destroy</tt>, skipping callbacks might bypass business logic in
-
# your application that ensures referential integrity or performs other
-
# essential jobs.
-
#
-
# ==== Examples
-
#
-
# # Delete a single row
-
# Todo.delete(1)
-
#
-
# # Delete multiple rows
-
# Todo.delete([2,3,4])
-
1
def delete(id_or_array)
-
where(primary_key => id_or_array).delete_all
-
end
-
-
# Causes the records to be loaded from the database if they have not
-
# been loaded already. You can use this if for some reason you need
-
# to explicitly load some records before actually using them. The
-
# return value is the relation itself, not the records.
-
#
-
# Post.where(published: true).load # => #<ActiveRecord::Relation>
-
1
def load
-
unless loaded?
-
# We monitor here the entire execution rather than individual SELECTs
-
# because from the point of view of the user fetching the records of a
-
# relation is a single unit of work. You want to know if this call takes
-
# too long, not if the individual queries take too long.
-
#
-
# It could be the case that none of the queries involved surpass the
-
# threshold, and at the same time the sum of them all does. The user
-
# should get a query plan logged in that case.
-
logging_query_plan { exec_queries }
-
end
-
-
self
-
end
-
-
# Forces reloading of relation.
-
1
def reload
-
reset
-
load
-
end
-
-
1
def reset
-
@first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
-
@should_eager_load = @join_dependency = nil
-
@records = []
-
self
-
end
-
-
# Returns sql statement for the relation.
-
#
-
# Users.where(name: 'Oscar').to_sql
-
# # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
-
1
def to_sql
-
@to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
-
end
-
-
# Returns a hash of where conditions
-
#
-
# Users.where(name: 'Oscar').where_values_hash
-
# # => {:name=>"oscar"}
-
1
def where_values_hash
-
equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
-
node.left.relation.name == table_name
-
}
-
-
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
-
-
Hash[equalities.map { |where|
-
name = where.left.name
-
[name, binds.fetch(name.to_s) { where.right }]
-
}]
-
end
-
-
1
def scope_for_create
-
@scope_for_create ||= where_values_hash.merge(create_with_value)
-
end
-
-
# Returns true if relation needs eager loading.
-
1
def eager_loading?
-
@should_eager_load ||=
-
eager_load_values.any? ||
-
includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
-
end
-
-
# Joins that are also marked for preloading. In which case we should just eager load them.
-
# Note that this is a naive implementation because we could have strings and symbols which
-
# represent the same association, but that aren't matched by this. Also, we could have
-
# nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
-
1
def joined_includes_values
-
includes_values & joins_values
-
end
-
-
# Compares two relations for equality.
-
1
def ==(other)
-
case other
-
when Relation
-
other.to_sql == to_sql
-
when Array
-
to_a == other
-
end
-
end
-
-
1
def pretty_print(q)
-
q.pp(self.to_a)
-
end
-
-
1
def with_default_scope #:nodoc:
-
if default_scoped? && default_scope = klass.send(:build_default_scope)
-
default_scope = default_scope.merge(self)
-
default_scope.default_scoped = false
-
default_scope
-
else
-
self
-
end
-
end
-
-
# Returns true if relation is blank.
-
1
def blank?
-
to_a.blank?
-
end
-
-
1
def values
-
Hash[@values]
-
end
-
-
1
def inspect
-
entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
-
entries[10] = '...' if entries.size == 11
-
-
"#<#{self.class.name} [#{entries.join(', ')}]>"
-
end
-
-
1
private
-
-
1
def exec_queries
-
default_scoped = with_default_scope
-
-
if default_scoped.equal?(self)
-
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
-
-
preload = preload_values
-
preload += includes_values unless eager_loading?
-
preload.each do |associations|
-
ActiveRecord::Associations::Preloader.new(@records, associations).run
-
end
-
-
# @readonly_value is true only if set explicitly. @implicit_readonly is true if there
-
# are JOINS and no explicit SELECT.
-
readonly = readonly_value.nil? ? @implicit_readonly : readonly_value
-
@records.each { |record| record.readonly! } if readonly
-
else
-
@records = default_scoped.to_a
-
end
-
-
@loaded = true
-
@records
-
end
-
-
1
def references_eager_loaded_tables?
-
joined_tables = arel.join_sources.map do |join|
-
if join.is_a?(Arel::Nodes::StringJoin)
-
tables_in_string(join.left)
-
else
-
[join.left.table_name, join.left.table_alias]
-
end
-
end
-
-
joined_tables += [table.name, table.table_alias]
-
-
# always convert table names to downcase as in Oracle quoted table names are in uppercase
-
joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
-
string_tables = tables_in_string(to_sql)
-
-
if (references_values - joined_tables).any?
-
true
-
elsif (string_tables - joined_tables).any?
-
ActiveSupport::Deprecation.warn(
-
"It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \
-
"that are referenced in a string SQL snippet. For example: \n" \
-
"\n" \
-
" Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
-
"\n" \
-
"Currently, Active Record recognises the table in the string, and knows to JOIN the " \
-
"comments table to the query, rather than loading comments in a separate query. " \
-
"However, doing this without writing a full-blown SQL parser is inherently flawed. " \
-
"Since we don't want to write an SQL parser, we are removing this functionality. " \
-
"From now on, you must explicitly tell Active Record when you are referencing a table " \
-
"from a string:\n" \
-
"\n" \
-
" Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n\n"
-
)
-
true
-
else
-
false
-
end
-
end
-
-
1
def tables_in_string(string)
-
return [] if string.blank?
-
# always convert table names to downcase as in Oracle quoted table names are in uppercase
-
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
-
string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module Batches
-
# Looping through a collection of records from the database
-
# (using the +all+ method, for example) is very inefficient
-
# since it will try to instantiate all the objects at once.
-
#
-
# In that case, batch processing methods allow you to work
-
# with the records in batches, thereby greatly reducing memory consumption.
-
#
-
# The #find_each method uses #find_in_batches with a batch size of 1000 (or as
-
# specified by the +:batch_size+ option).
-
#
-
# Person.all.find_each do |person|
-
# person.do_awesome_stuff
-
# end
-
#
-
# Person.where("age > 21").find_each do |person|
-
# person.party_all_night!
-
# end
-
#
-
# You can also pass the +:start+ option to specify
-
# an offset to control the starting point.
-
1
def find_each(options = {})
-
find_in_batches(options) do |records|
-
records.each { |record| yield record }
-
end
-
end
-
-
# Yields each batch of records that was found by the find +options+ as
-
# an array. The size of each batch is set by the +:batch_size+
-
# option; the default is 1000.
-
#
-
# You can control the starting point for the batch processing by
-
# supplying the +:start+ option. This is especially useful if you
-
# want multiple workers dealing with the same processing queue. You can
-
# make worker 1 handle all the records between id 0 and 10,000 and
-
# worker 2 handle from 10,000 and beyond (by setting the +:start+
-
# option on that worker).
-
#
-
# It's not possible to set the order. That is automatically set to
-
# ascending on the primary key ("id ASC") to make the batch ordering
-
# work. This also means that this method only works with integer-based
-
# primary keys. You can't set the limit either, that's used to control
-
# the batch sizes.
-
#
-
# Person.where("age > 21").find_in_batches do |group|
-
# sleep(50) # Make sure it doesn't get too crowded in there!
-
# group.each { |person| person.party_all_night! }
-
# end
-
#
-
# # Let's process the next 2000 records
-
# Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
-
# group.each { |person| person.party_all_night! }
-
# end
-
1
def find_in_batches(options = {})
-
options.assert_valid_keys(:start, :batch_size)
-
-
relation = self
-
-
unless arel.orders.blank? && arel.taken.blank?
-
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
-
end
-
-
start = options.delete(:start)
-
batch_size = options.delete(:batch_size) || 1000
-
-
relation = relation.reorder(batch_order).limit(batch_size)
-
records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
-
-
while records.any?
-
records_size = records.size
-
primary_key_offset = records.last.id
-
-
yield records
-
-
break if records_size < batch_size
-
-
if primary_key_offset
-
records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
-
else
-
raise "Primary key not included in the custom select clause"
-
end
-
end
-
end
-
-
1
private
-
-
1
def batch_order
-
"#{quoted_table_name}.#{quoted_primary_key} ASC"
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/try'
-
-
1
module ActiveRecord
-
1
module Calculations
-
# Count the records.
-
#
-
# Person.count
-
# # => the total count of all people
-
#
-
# Person.count(:age)
-
# # => returns the total count of all people whose age is present in database
-
#
-
# Person.count(:all)
-
# # => performs a COUNT(*) (:all is an alias for '*')
-
#
-
# Person.count(:age, distinct: true)
-
# # => counts the number of different age values
-
#
-
# Person.where("age > 26").count { |person| person.gender == 'female' }
-
# # => queries people where "age > 26" then count the loaded results filtering by gender
-
1
def count(column_name = nil, options = {})
-
if block_given?
-
self.to_a.count { |item| yield item }
-
else
-
column_name, options = nil, column_name if column_name.is_a?(Hash)
-
calculate(:count, column_name, options)
-
end
-
end
-
-
# Calculates the average value on a given column. Returns +nil+ if there's
-
# no row. See +calculate+ for examples with options.
-
#
-
# Person.average('age') # => 35.8
-
1
def average(column_name, options = {})
-
calculate(:average, column_name, options)
-
end
-
-
# Calculates the minimum value on a given column. The value is returned
-
# with the same data type of the column, or +nil+ if there's no row. See
-
# +calculate+ for examples with options.
-
#
-
# Person.minimum('age') # => 7
-
1
def minimum(column_name, options = {})
-
calculate(:minimum, column_name, options)
-
end
-
-
# Calculates the maximum value on a given column. The value is returned
-
# with the same data type of the column, or +nil+ if there's no row. See
-
# +calculate+ for examples with options.
-
#
-
# Person.maximum('age') # => 93
-
1
def maximum(column_name, options = {})
-
calculate(:maximum, column_name, options)
-
end
-
-
# Calculates the sum of values on a given column. The value is returned
-
# with the same data type of the column, 0 if there's no row. See
-
# +calculate+ for examples with options.
-
#
-
# Person.sum('age') # => 4562
-
# # => returns the total sum of all people's age
-
#
-
# Person.where('age > 100').sum { |person| person.age - 100 }
-
# # queries people where "age > 100" then perform a sum calculation with the block returns
-
1
def sum(*args)
-
if block_given?
-
self.to_a.sum(*args) { |item| yield item }
-
else
-
calculate(:sum, *args)
-
end
-
end
-
-
# This calculates aggregate values in the given column. Methods for count, sum, average,
-
# minimum, and maximum have been added as shortcuts.
-
#
-
# There are two basic forms of output:
-
#
-
# * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
-
# for AVG, and the given column's type for everything else.
-
#
-
# * Grouped values: This returns an ordered hash of the values and groups them. It
-
# takes either a column name, or the name of a belongs_to association.
-
#
-
# values = Person.group('last_name').maximum(:age)
-
# puts values["Drake"]
-
# => 43
-
#
-
# drake = Family.find_by_last_name('Drake')
-
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
-
# puts values[drake]
-
# => 43
-
#
-
# values.each do |family, max_age|
-
# ...
-
# end
-
#
-
# Examples:
-
# Person.calculate(:count, :all) # The same as Person.count
-
# Person.average(:age) # SELECT AVG(age) FROM people...
-
#
-
# # Selects the minimum age for any family without any minors
-
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
-
#
-
# Person.sum("2 * age")
-
1
def calculate(operation, column_name, options = {})
-
relation = with_default_scope
-
-
if relation.equal?(self)
-
if has_include?(column_name)
-
construct_relation_for_association_calculations.calculate(operation, column_name, options)
-
else
-
perform_calculation(operation, column_name, options)
-
end
-
else
-
relation.calculate(operation, column_name, options)
-
end
-
rescue ThrowResult
-
0
-
end
-
-
# Use <tt>pluck</tt> as a shortcut to select a single attribute without
-
# loading a bunch of records just to grab one attribute you want.
-
#
-
# Person.pluck(:name)
-
#
-
# instead of
-
#
-
# Person.all.map(&:name)
-
#
-
# Pluck returns an <tt>Array</tt> of attribute values type-casted to match
-
# the plucked column name, if it can be deduced. Plucking an SQL fragment
-
# returns String values by default.
-
#
-
# Examples:
-
#
-
# Person.pluck(:id)
-
# # SELECT people.id FROM people
-
# # => [1, 2, 3]
-
#
-
# Person.pluck(:id, :name)
-
# # SELECT people.id, people.name FROM people
-
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
-
#
-
# Person.uniq.pluck(:role)
-
# # SELECT DISTINCT role FROM people
-
# # => ['admin', 'member', 'guest']
-
#
-
# Person.where(:age => 21).limit(5).pluck(:id)
-
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
-
# # => [2, 3]
-
#
-
# Person.pluck('DATEDIFF(updated_at, created_at)')
-
# # SELECT DATEDIFF(updated_at, created_at) FROM people
-
# # => ['0', '27761', '173']
-
#
-
1
def pluck(*column_names)
-
column_names.map! do |column_name|
-
if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
-
"#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
-
else
-
column_name
-
end
-
end
-
-
if has_include?(column_names.first)
-
construct_relation_for_association_calculations.pluck(*column_names)
-
else
-
result = klass.connection.select_all(select(column_names).arel, nil, bind_values)
-
columns = result.columns.map do |key|
-
klass.column_types.fetch(key) {
-
result.column_types.fetch(key) {
-
Class.new { def type_cast(v); v; end }.new
-
}
-
}
-
end
-
-
result = result.map do |attributes|
-
values = klass.initialize_attributes(attributes).values
-
-
columns.zip(values).map do |column, value|
-
column.type_cast(value)
-
end
-
end
-
columns.one? ? result.map!(&:first) : result
-
end
-
end
-
-
# Pluck all the ID's for the relation using the table's primary key
-
#
-
# Examples:
-
#
-
# Person.ids # SELECT people.id FROM people
-
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
-
1
def ids
-
pluck primary_key
-
end
-
-
1
private
-
-
1
def has_include?(column_name)
-
eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
-
end
-
-
1
def perform_calculation(operation, column_name, options = {})
-
operation = operation.to_s.downcase
-
-
distinct = options[:distinct]
-
-
if operation == "count"
-
column_name ||= (select_for_count || :all)
-
-
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
-
distinct = true
-
end
-
-
column_name = primary_key if column_name == :all && distinct
-
-
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
-
end
-
-
if group_values.any?
-
execute_grouped_calculation(operation, column_name, distinct)
-
else
-
execute_simple_calculation(operation, column_name, distinct)
-
end
-
end
-
-
1
def aggregate_column(column_name)
-
if @klass.column_names.include?(column_name.to_s)
-
Arel::Attribute.new(@klass.unscoped.table, column_name)
-
else
-
Arel.sql(column_name == :all ? "*" : column_name.to_s)
-
end
-
end
-
-
1
def operation_over_aggregate_column(column, operation, distinct)
-
operation == 'count' ? column.count(distinct) : column.send(operation)
-
end
-
-
1
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
-
# Postgresql doesn't like ORDER BY when there are no GROUP BY
-
relation = reorder(nil)
-
-
if operation == "count" && (relation.limit_value || relation.offset_value)
-
# Shortcut when limit is zero.
-
return 0 if relation.limit_value == 0
-
-
query_builder = build_count_subquery(relation, column_name, distinct)
-
else
-
column = aggregate_column(column_name)
-
-
select_value = operation_over_aggregate_column(column, operation, distinct)
-
-
relation.select_values = [select_value]
-
-
query_builder = relation.arel
-
end
-
-
result = @klass.connection.select_value(query_builder, nil, relation.bind_values)
-
type_cast_calculated_value(result, column_for(column_name), operation)
-
end
-
-
1
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
-
group_attrs = group_values
-
-
if group_attrs.first.respond_to?(:to_sym)
-
association = @klass.reflect_on_association(group_attrs.first.to_sym)
-
associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
-
group_fields = Array(associated ? association.foreign_key : group_attrs)
-
else
-
group_fields = group_attrs
-
end
-
-
group_aliases = group_fields.map { |field| column_alias_for(field) }
-
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
-
[aliaz, column_for(field)]
-
}
-
-
group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
-
-
if operation == 'count' && column_name == :all
-
aggregate_alias = 'count_all'
-
else
-
aggregate_alias = column_alias_for(operation, column_name)
-
end
-
-
select_values = [
-
operation_over_aggregate_column(
-
aggregate_column(column_name),
-
operation,
-
distinct).as(aggregate_alias)
-
]
-
select_values += select_values unless having_values.empty?
-
-
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
-
if field.respond_to?(:as)
-
field.as(aliaz)
-
else
-
"#{field} AS #{aliaz}"
-
end
-
}
-
-
relation = except(:group).group(group)
-
relation.select_values = select_values
-
-
calculated_data = @klass.connection.select_all(relation, nil, bind_values)
-
-
if association
-
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
-
key_records = association.klass.base_class.find(key_ids)
-
key_records = Hash[key_records.map { |r| [r.id, r] }]
-
end
-
-
Hash[calculated_data.map do |row|
-
key = group_columns.map { |aliaz, column|
-
type_cast_calculated_value(row[aliaz], column)
-
}
-
key = key.first if key.size == 1
-
key = key_records[key] if associated
-
[key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
-
end]
-
end
-
-
# Converts the given keys to the value that the database adapter returns as
-
# a usable column name:
-
#
-
# column_alias_for("users.id") # => "users_id"
-
# column_alias_for("sum(id)") # => "sum_id"
-
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
-
# column_alias_for("count(*)") # => "count_all"
-
# column_alias_for("count", "id") # => "count_id"
-
1
def column_alias_for(*keys)
-
keys.map! {|k| k.respond_to?(:to_sql) ? k.to_sql : k}
-
table_name = keys.join(' ')
-
table_name.downcase!
-
table_name.gsub!(/\*/, 'all')
-
table_name.gsub!(/\W+/, ' ')
-
table_name.strip!
-
table_name.gsub!(/ +/, '_')
-
-
@klass.connection.table_alias_for(table_name)
-
end
-
-
1
def column_for(field)
-
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
-
@klass.columns_hash[field_name]
-
end
-
-
1
def type_cast_calculated_value(value, column, operation = nil)
-
case operation
-
when 'count' then value.to_i
-
when 'sum' then type_cast_using_column(value || 0, column)
-
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
-
else type_cast_using_column(value, column)
-
end
-
end
-
-
1
def type_cast_using_column(value, column)
-
column ? column.type_cast(value) : value
-
end
-
-
1
def select_for_count
-
if select_values.present?
-
select = select_values.join(", ")
-
select if select !~ /[,*]/
-
end
-
end
-
-
1
def build_count_subquery(relation, column_name, distinct)
-
column_alias = Arel.sql('count_column')
-
subquery_alias = Arel.sql('subquery_for_count')
-
-
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
-
relation.select_values = [aliased_column]
-
subquery = relation.arel.as(subquery_alias)
-
-
sm = Arel::SelectManager.new relation.engine
-
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
-
sm.project(select_value).from(subquery)
-
end
-
end
-
end
-
1
require 'thread'
-
-
1
module ActiveRecord
-
1
module Delegation # :nodoc:
-
# Set up common delegations for performance (avoids method_missing)
-
1
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
-
1
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
-
:connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
-
-
1
@@delegation_mutex = Mutex.new
-
-
1
def self.delegate_to_scoped_klass(method)
-
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
-
module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{method}(*args, &block)
-
scoping { @klass.#{method}(*args, &block) }
-
end
-
RUBY
-
else
-
module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{method}(*args, &block)
-
scoping { @klass.send(#{method.inspect}, *args, &block) }
-
end
-
RUBY
-
end
-
end
-
-
1
def respond_to?(method, include_private = false)
-
super || Array.method_defined?(method) ||
-
@klass.respond_to?(method, include_private) ||
-
arel.respond_to?(method, include_private)
-
end
-
-
1
protected
-
-
1
def method_missing(method, *args, &block)
-
if @klass.respond_to?(method)
-
@@delegation_mutex.synchronize do
-
unless ::ActiveRecord::Delegation.method_defined?(method)
-
::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
-
end
-
end
-
-
scoping { @klass.send(method, *args, &block) }
-
elsif Array.method_defined?(method)
-
@@delegation_mutex.synchronize do
-
unless ::ActiveRecord::Delegation.method_defined?(method)
-
::ActiveRecord::Delegation.delegate method, :to => :to_a
-
end
-
end
-
-
to_a.send(method, *args, &block)
-
elsif arel.respond_to?(method)
-
@@delegation_mutex.synchronize do
-
unless ::ActiveRecord::Delegation.method_defined?(method)
-
::ActiveRecord::Delegation.delegate method, :to => :arel
-
end
-
end
-
-
arel.send(method, *args, &block)
-
else
-
super
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module FinderMethods
-
# Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
-
# If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
-
# is an integer, find by id coerces its arguments using +to_i+.
-
#
-
# Person.find(1) # returns the object for ID = 1
-
# Person.find("1") # returns the object for ID = 1
-
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
-
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
-
# Person.find([1]) # returns an array for the object with ID = 1
-
# Person.where("administrator = 1").order("created_on DESC").find(1)
-
#
-
# Note that returned records may not be in the same order as the ids you
-
# provide since database rows are unordered. Give an explicit <tt>order</tt>
-
# to ensure the results are sorted.
-
#
-
# ==== Find with lock
-
#
-
# Example for find with a lock: Imagine two concurrent transactions:
-
# each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
-
# in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
-
# transaction has to wait until the first is finished; we get the
-
# expected <tt>person.visits == 4</tt>.
-
#
-
# Person.transaction do
-
# person = Person.lock(true).find(1)
-
# person.visits += 1
-
# person.save!
-
# end
-
1
def find(*args)
-
if block_given?
-
to_a.find { |*block_args| yield(*block_args) }
-
else
-
find_with_ids(*args)
-
end
-
end
-
-
# Finds the first record matching the specified conditions. There
-
# is no implied ording so if order matters, you should specify it
-
# yourself.
-
#
-
# If no record is found, returns <tt>nil</tt>.
-
#
-
# Post.find_by name: 'Spartacus', rating: 4
-
# Post.find_by "published_at < ?", 2.weeks.ago
-
1
def find_by(*args)
-
where(*args).take
-
end
-
-
# Like <tt>find_by</tt>, except that if no record is found, raises
-
# an <tt>ActiveRecord::RecordNotFound</tt> error.
-
1
def find_by!(*args)
-
where(*args).take!
-
end
-
-
# Gives a record (or N records if a parameter is supplied) without any implied
-
# order. The order will depend on the database implementation.
-
# If an order is supplied it will be respected.
-
#
-
# Person.take # returns an object fetched by SELECT * FROM people
-
# Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
-
# Person.where(["name LIKE '%?'", name]).take
-
1
def take(limit = nil)
-
limit ? limit(limit).to_a : find_take
-
end
-
-
# Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found. Note that <tt>take!</tt> accepts no arguments.
-
1
def take!
-
take or raise RecordNotFound
-
end
-
-
# Find the first record (or first N records if a parameter is supplied).
-
# If no order is defined it will order by primary key.
-
#
-
# Person.first # returns the first object fetched by SELECT * FROM people
-
# Person.where(["user_name = ?", user_name]).first
-
# Person.where(["user_name = :u", { :u => user_name }]).first
-
# Person.order("created_on DESC").offset(5).first
-
# Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
-
1
def first(limit = nil)
-
if limit
-
if order_values.empty? && primary_key
-
order(arel_table[primary_key].asc).limit(limit).to_a
-
else
-
limit(limit).to_a
-
end
-
else
-
find_first
-
end
-
end
-
-
# Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found. Note that <tt>first!</tt> accepts no arguments.
-
1
def first!
-
first or raise RecordNotFound
-
end
-
-
# Find the last record (or last N records if a parameter is supplied).
-
# If no order is defined it will order by primary key.
-
#
-
# Person.last # returns the last object fetched by SELECT * FROM people
-
# Person.where(["user_name = ?", user_name]).last
-
# Person.order("created_on DESC").offset(5).last
-
# Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
-
#
-
# Take note that in that last case, the results are sorted in ascending order:
-
#
-
# [#<Person id:2>, #<Person id:3>, #<Person id:4>]
-
#
-
# and not:
-
#
-
# [#<Person id:4>, #<Person id:3>, #<Person id:2>]
-
1
def last(limit = nil)
-
if limit
-
if order_values.empty? && primary_key
-
order(arel_table[primary_key].desc).limit(limit).reverse
-
else
-
to_a.last(limit)
-
end
-
else
-
find_last
-
end
-
end
-
-
# Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found. Note that <tt>last!</tt> accepts no arguments.
-
1
def last!
-
last or raise RecordNotFound
-
end
-
-
# Returns +true+ if a record exists in the table that matches the +id+ or
-
# conditions given, or +false+ otherwise. The argument can take six forms:
-
#
-
# * Integer - Finds the record with this primary key.
-
# * String - Finds the record with a primary key corresponding to this
-
# string (such as <tt>'5'</tt>).
-
# * Array - Finds the record that matches these +find+-style conditions
-
# (such as <tt>['color = ?', 'red']</tt>).
-
# * Hash - Finds the record that matches these +find+-style conditions
-
# (such as <tt>{color: 'red'}</tt>).
-
# * +false+ - Returns always +false+.
-
# * No args - Returns +false+ if the table is empty, +true+ otherwise.
-
#
-
# For more information about specifying conditions as a Hash or Array,
-
# see the Conditions section in the introduction to ActiveRecord::Base.
-
#
-
# Note: You can't pass in a condition as a string (like <tt>name =
-
# 'Jamie'</tt>), since it would be sanitized and then queried against
-
# the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
-
#
-
# Person.exists?(5)
-
# Person.exists?('5')
-
# Person.exists?(['name LIKE ?', "%#{query}%"])
-
# Person.exists?(name: 'David')
-
# Person.exists?(false)
-
# Person.exists?
-
1
def exists?(conditions = :none)
-
conditions = conditions.id if Base === conditions
-
return false if !conditions
-
-
join_dependency = construct_join_dependency_for_association_find
-
relation = construct_relation_for_association_find(join_dependency)
-
relation = relation.except(:select, :order).select("1 AS one").limit(1)
-
-
case conditions
-
when Array, Hash
-
relation = relation.where(conditions)
-
else
-
relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
-
end
-
-
connection.select_value(relation, "#{name} Exists", relation.bind_values)
-
rescue ThrowResult
-
false
-
end
-
-
1
protected
-
-
1
def find_with_associations
-
join_dependency = construct_join_dependency_for_association_find
-
relation = construct_relation_for_association_find(join_dependency)
-
rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
-
join_dependency.instantiate(rows)
-
rescue ThrowResult
-
[]
-
end
-
-
1
def construct_join_dependency_for_association_find
-
including = (eager_load_values + includes_values).uniq
-
ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
-
end
-
-
1
def construct_relation_for_association_calculations
-
including = (eager_load_values + includes_values).uniq
-
join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
-
relation = except(:includes, :eager_load, :preload)
-
apply_join_dependency(relation, join_dependency)
-
end
-
-
1
def construct_relation_for_association_find(join_dependency)
-
relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
-
apply_join_dependency(relation, join_dependency)
-
end
-
-
1
def apply_join_dependency(relation, join_dependency)
-
join_dependency.join_associations.each do |association|
-
relation = association.join_relation(relation)
-
end
-
-
limitable_reflections = using_limitable_reflections?(join_dependency.reflections)
-
-
if !limitable_reflections && relation.limit_value
-
limited_id_condition = construct_limited_ids_condition(relation.except(:select))
-
relation = relation.where(limited_id_condition)
-
end
-
-
relation = relation.except(:limit, :offset) unless limitable_reflections
-
-
relation
-
end
-
-
1
def construct_limited_ids_condition(relation)
-
orders = relation.order_values.map { |val| val.presence }.compact
-
values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
-
-
relation = relation.dup
-
-
ids_array = relation.select(values).collect {|row| row[primary_key]}
-
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
-
end
-
-
1
def find_with_ids(*ids)
-
expects_array = ids.first.kind_of?(Array)
-
return ids.first if expects_array && ids.first.empty?
-
-
ids = ids.flatten.compact.uniq
-
-
case ids.size
-
when 0
-
raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
-
when 1
-
result = find_one(ids.first)
-
expects_array ? [ result ] : result
-
else
-
find_some(ids)
-
end
-
end
-
-
1
def find_one(id)
-
id = id.id if ActiveRecord::Base === id
-
-
column = columns_hash[primary_key]
-
substitute = connection.substitute_at(column, bind_values.length)
-
relation = where(table[primary_key].eq(substitute))
-
relation.bind_values += [[column, id]]
-
record = relation.take
-
-
unless record
-
conditions = arel.where_sql
-
conditions = " [#{conditions}]" if conditions
-
raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
-
end
-
-
record
-
end
-
-
1
def find_some(ids)
-
result = where(table[primary_key].in(ids)).to_a
-
-
expected_size =
-
if limit_value && ids.size > limit_value
-
limit_value
-
else
-
ids.size
-
end
-
-
# 11 ids with limit 3, offset 9 should give 2 results.
-
if offset_value && (ids.size - offset_value < expected_size)
-
expected_size = ids.size - offset_value
-
end
-
-
if result.size == expected_size
-
result
-
else
-
conditions = arel.where_sql
-
conditions = " [#{conditions}]" if conditions
-
-
error = "Couldn't find all #{@klass.name.pluralize} with IDs "
-
error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
-
raise RecordNotFound, error
-
end
-
end
-
-
1
def find_take
-
if loaded?
-
@records.first
-
else
-
@take ||= limit(1).to_a.first
-
end
-
end
-
-
1
def find_first
-
if loaded?
-
@records.first
-
else
-
@first ||=
-
if with_default_scope.order_values.empty? && primary_key
-
order(arel_table[primary_key].asc).limit(1).to_a.first
-
else
-
limit(1).to_a.first
-
end
-
end
-
end
-
-
1
def find_last
-
if loaded?
-
@records.last
-
else
-
@last ||=
-
if offset_value || limit_value
-
to_a.last
-
else
-
reverse_order.limit(1).to_a.first
-
end
-
end
-
end
-
-
1
def using_limitable_reflections?(reflections)
-
reflections.none? { |r| r.collection? }
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/keys'
-
-
1
module ActiveRecord
-
1
class Relation
-
1
class HashMerger # :nodoc:
-
1
attr_reader :relation, :hash
-
-
1
def initialize(relation, hash)
-
hash.assert_valid_keys(*Relation::VALUE_METHODS)
-
-
@relation = relation
-
@hash = hash
-
end
-
-
1
def merge
-
Merger.new(relation, other).merge
-
end
-
-
# Applying values to a relation has some side effects. E.g.
-
# interpolation might take place for where values. So we should
-
# build a relation to merge in rather than directly merging
-
# the values.
-
1
def other
-
other = Relation.new(relation.klass, relation.table)
-
hash.each { |k, v|
-
if k == :joins
-
if Hash === v
-
other.joins!(v)
-
else
-
other.joins!(*v)
-
end
-
else
-
other.send("#{k}!", v)
-
end
-
}
-
other
-
end
-
end
-
-
1
class Merger # :nodoc:
-
1
attr_reader :relation, :values
-
-
1
def initialize(relation, other)
-
if other.default_scoped? && other.klass != relation.klass
-
other = other.with_default_scope
-
end
-
-
@relation = relation
-
@values = other.values
-
end
-
-
1
NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
-
Relation::MULTI_VALUE_METHODS -
-
[:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
-
-
1
def normal_values
-
NORMAL_VALUES
-
end
-
-
1
def merge
-
normal_values.each do |name|
-
value = values[name]
-
relation.send("#{name}!", *value) unless value.blank?
-
end
-
-
merge_multi_values
-
merge_single_values
-
-
relation
-
end
-
-
1
private
-
-
1
def merge_multi_values
-
relation.where_values = merged_wheres
-
relation.bind_values = merged_binds
-
-
if values[:reordering]
-
# override any order specified in the original relation
-
relation.reorder! values[:order]
-
elsif values[:order]
-
# merge in order_values from r
-
relation.order! values[:order]
-
end
-
-
relation.extend(*values[:extending]) unless values[:extending].blank?
-
end
-
-
1
def merge_single_values
-
relation.from_value = values[:from] unless relation.from_value
-
relation.lock_value = values[:lock] unless relation.lock_value
-
relation.reverse_order_value = values[:reverse_order]
-
-
unless values[:create_with].blank?
-
relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
-
end
-
end
-
-
1
def merged_binds
-
if values[:bind]
-
(relation.bind_values + values[:bind]).uniq(&:first)
-
else
-
relation.bind_values
-
end
-
end
-
-
1
def merged_wheres
-
if values[:where]
-
merged_wheres = relation.where_values + values[:where]
-
-
unless relation.where_values.empty?
-
# Remove equalities with duplicated left-hand. Last one wins.
-
seen = {}
-
merged_wheres = merged_wheres.reverse.reject { |w|
-
nuke = false
-
if w.respond_to?(:operator) && w.operator == :==
-
nuke = seen[w.left]
-
seen[w.left] = true
-
end
-
nuke
-
}.reverse
-
end
-
-
merged_wheres
-
else
-
relation.where_values
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/wrap'
-
-
1
module ActiveRecord
-
1
module QueryMethods
-
1
extend ActiveSupport::Concern
-
-
1
Relation::MULTI_VALUE_METHODS.each do |name|
-
12
class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}_values # def select_values
-
@values[:#{name}] || [] # @values[:select] || []
-
end # end
-
#
-
def #{name}_values=(values) # def select_values=(values)
-
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
-
@values[:#{name}] = values # @values[:select] = values
-
end # end
-
CODE
-
end
-
-
1
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
-
8
class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}_value # def readonly_value
-
@values[:#{name}] # @values[:readonly]
-
end # end
-
CODE
-
end
-
-
1
Relation::SINGLE_VALUE_METHODS.each do |name|
-
9
class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}_value=(value) # def readonly_value=(value)
-
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
-
@values[:#{name}] = value # @values[:readonly] = value
-
end # end
-
CODE
-
end
-
-
1
def create_with_value # :nodoc:
-
@values[:create_with] || {}
-
end
-
-
1
alias extensions extending_values
-
-
# Specify relationships to be included in the result set. For
-
# example:
-
#
-
# users = User.includes(:address)
-
# users.each do |user|
-
# user.address.city
-
# end
-
#
-
# allows you to access the +address+ attribute of the +User+ model without
-
# firing an additional query. This will often result in a
-
# performance improvement over a simple +join+.
-
#
-
# === conditions
-
#
-
# If you want to add conditions to your included models you'll have
-
# to explicitly reference them. For example:
-
#
-
# User.includes(:posts).where('posts.name = ?', 'example')
-
#
-
# Will throw an error, but this will work:
-
#
-
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
-
1
def includes(*args)
-
args.empty? ? self : spawn.includes!(*args)
-
end
-
-
# Like #includes, but modifies the relation in place.
-
1
def includes!(*args)
-
args.reject! {|a| a.blank? }
-
-
self.includes_values = (includes_values + args).flatten.uniq
-
self
-
end
-
-
# Forces eager loading by performing a LEFT OUTER JOIN on +args+:
-
#
-
# User.eager_load(:posts)
-
# => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
-
# FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
-
# "users"."id"
-
1
def eager_load(*args)
-
args.blank? ? self : spawn.eager_load!(*args)
-
end
-
-
# Like #eager_load, but modifies relation in place.
-
1
def eager_load!(*args)
-
self.eager_load_values += args
-
self
-
end
-
-
# Allows preloading of +args+, in the same way that +includes+ does:
-
#
-
# User.preload(:posts)
-
# => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
-
1
def preload(*args)
-
args.blank? ? self : spawn.preload!(*args)
-
end
-
-
# Like #preload, but modifies relation in place.
-
1
def preload!(*args)
-
self.preload_values += args
-
self
-
end
-
-
# Used to indicate that an association is referenced by an SQL string, and should
-
# therefore be JOINed in any query rather than loaded separately.
-
#
-
# User.includes(:posts).where("posts.name = 'foo'")
-
# # => Doesn't JOIN the posts table, resulting in an error.
-
#
-
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
-
# # => Query now knows the string references posts, so adds a JOIN
-
1
def references(*args)
-
args.blank? ? self : spawn.references!(*args)
-
end
-
-
# Like #references, but modifies relation in place.
-
1
def references!(*args)
-
args.flatten!
-
-
self.references_values = (references_values + args.map!(&:to_s)).uniq
-
self
-
end
-
-
# Works in two unique ways.
-
#
-
# First: takes a block so it can be used just like Array#select.
-
#
-
# Model.all.select { |m| m.field == value }
-
#
-
# This will build an array of objects from the database for the scope,
-
# converting them into an array and iterating through them using Array#select.
-
#
-
# Second: Modifies the SELECT statement for the query so that only certain
-
# fields are retrieved:
-
#
-
# Model.select(:field)
-
# # => [#<Model field:value>]
-
#
-
# Although in the above example it looks as though this method returns an
-
# array, it actually returns a relation object and can have other query
-
# methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
-
#
-
# The argument to the method can also be an array of fields.
-
#
-
# Model.select(:field, :other_field, :and_one_more)
-
# # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
-
#
-
# Accessing attributes of an object that do not have fields retrieved by a select
-
# will throw <tt>ActiveModel::MissingAttributeError</tt>:
-
#
-
# Model.select(:field).first.other_field
-
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
-
1
def select(*fields)
-
if block_given?
-
to_a.select { |*block_args| yield(*block_args) }
-
else
-
raise ArgumentError, 'Call this with at least one field' if fields.empty?
-
spawn.select!(*fields)
-
end
-
end
-
-
# Like #select, but modifies relation in place.
-
1
def select!(*fields)
-
self.select_values += fields.flatten
-
self
-
end
-
-
# Allows to specify a group attribute:
-
#
-
# User.group(:name)
-
# => SELECT "users".* FROM "users" GROUP BY name
-
#
-
# Returns an array with distinct records based on the +group+ attribute:
-
#
-
# User.select([:id, :name])
-
# => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
-
#
-
# User.group(:name)
-
# => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
-
1
def group(*args)
-
args.blank? ? self : spawn.group!(*args)
-
end
-
-
# Like #group, but modifies relation in place.
-
1
def group!(*args)
-
args.flatten!
-
-
self.group_values += args
-
self
-
end
-
-
# Allows to specify an order attribute:
-
#
-
# User.order('name')
-
# => SELECT "users".* FROM "users" ORDER BY name
-
#
-
# User.order('name DESC')
-
# => SELECT "users".* FROM "users" ORDER BY name DESC
-
#
-
# User.order('name DESC, email')
-
# => SELECT "users".* FROM "users" ORDER BY name DESC, email
-
#
-
# User.order(:name)
-
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
-
#
-
# User.order(email: :desc)
-
# => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
-
#
-
# User.order(:name, email: :desc)
-
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
-
1
def order(*args)
-
args.blank? ? self : spawn.order!(*args)
-
end
-
-
# Like #order, but modifies relation in place.
-
1
def order!(*args)
-
args.flatten!
-
validate_order_args args
-
-
references = args.reject { |arg| Arel::Node === arg }
-
references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
-
references!(references) if references.any?
-
-
self.order_values = args + self.order_values
-
self
-
end
-
-
# Replaces any existing order defined on the relation with the specified order.
-
#
-
# User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
-
#
-
# Subsequent calls to order on the same relation will be appended. For example:
-
#
-
# User.order('email DESC').reorder('id ASC').order('name ASC')
-
#
-
# generates a query with 'ORDER BY name ASC, id ASC'.
-
1
def reorder(*args)
-
args.blank? ? self : spawn.reorder!(*args)
-
end
-
-
# Like #reorder, but modifies relation in place.
-
1
def reorder!(*args)
-
args.flatten!
-
validate_order_args args
-
-
self.reordering_value = true
-
self.order_values = args
-
self
-
end
-
-
# Performs a joins on +args+:
-
#
-
# User.joins(:posts)
-
# => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
-
1
def joins(*args)
-
args.compact.blank? ? self : spawn.joins!(*args.flatten)
-
end
-
-
# Like #joins, but modifies relation in place.
-
1
def joins!(*args)
-
self.joins_values += args
-
self
-
end
-
-
1
def bind(value)
-
spawn.bind!(value)
-
end
-
-
1
def bind!(value)
-
self.bind_values += [value]
-
self
-
end
-
-
# Returns a new relation, which is the result of filtering the current relation
-
# according to the conditions in the arguments.
-
#
-
# #where accepts conditions in one of several formats. In the examples below, the resulting
-
# SQL is given as an illustration; the actual query generated may be different depending
-
# on the database adapter.
-
#
-
# === string
-
#
-
# A single string, without additional arguments, is passed to the query
-
# constructor as a SQL fragment, and used in the where clause of the query.
-
#
-
# Client.where("orders_count = '2'")
-
# # SELECT * from clients where orders_count = '2';
-
#
-
# Note that building your own string from user input may expose your application
-
# to injection attacks if not done properly. As an alternative, it is recommended
-
# to use one of the following methods.
-
#
-
# === array
-
#
-
# If an array is passed, then the first element of the array is treated as a template, and
-
# the remaining elements are inserted into the template to generate the condition.
-
# Active Record takes care of building the query to avoid injection attacks, and will
-
# convert from the ruby type to the database type where needed. Elements are inserted
-
# into the string in the order in which they appear.
-
#
-
# User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# Alternatively, you can use named placeholders in the template, and pass a hash as the
-
# second element of the array. The names in the template are replaced with the corresponding
-
# values from the hash.
-
#
-
# User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# This can make for more readable code in complex queries.
-
#
-
# Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
-
# than the previous methods; you are responsible for ensuring that the values in the template
-
# are properly quoted. The values are passed to the connector for quoting, but the caller
-
# is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
-
# the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
-
#
-
# User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# If #where is called with multiple arguments, these are treated as if they were passed as
-
# the elements of a single array.
-
#
-
# User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# When using strings to specify conditions, you can use any operator available from
-
# the database. While this provides the most flexibility, you can also unintentionally introduce
-
# dependencies on the underlying database. If your code is intended for general consumption,
-
# test with multiple database backends.
-
#
-
# === hash
-
#
-
# #where will also accept a hash condition, in which the keys are fields and the values
-
# are values to be searched for.
-
#
-
# Fields can be symbols or strings. Values can be single values, arrays, or ranges.
-
#
-
# User.where({ name: "Joe", email: "joe@example.com" })
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
-
#
-
# User.where({ name: ["Alice", "Bob"]})
-
# # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
-
#
-
# User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
-
# # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
-
#
-
# In the case of a belongs_to relationship, an association key can be used
-
# to specify the model if an ActiveRecord object is used as the value.
-
#
-
# author = Author.find(1)
-
#
-
# # The following queries will be equivalent:
-
# Post.where(:author => author)
-
# Post.where(:author_id => author)
-
#
-
# This also works with polymorphic belongs_to relationships:
-
#
-
# treasure = Treasure.create(:name => 'gold coins')
-
# treasure.price_estimates << PriceEstimate.create(:price => 125)
-
#
-
# # The following queries will be equivalent:
-
# PriceEstimate.where(:estimate_of => treasure)
-
# PriceEstimate.where(:estimate_of_type => 'Treasure', :estimate_of_id => treasure)
-
#
-
# === Joins
-
#
-
# If the relation is the result of a join, you may create a condition which uses any of the
-
# tables in the join. For string and array conditions, use the table name in the condition.
-
#
-
# User.joins(:posts).where("posts.created_at < ?", Time.now)
-
#
-
# For hash conditions, you can either use the table name in the key, or use a sub-hash.
-
#
-
# User.joins(:posts).where({ "posts.published" => true })
-
# User.joins(:posts).where({ :posts => { :published => true } })
-
#
-
# === empty condition
-
#
-
# If the condition returns true for blank?, then where is a no-op and returns the current relation.
-
1
def where(opts, *rest)
-
opts.blank? ? self : spawn.where!(opts, *rest)
-
end
-
-
# #where! is identical to #where, except that instead of returning a new relation, it adds
-
# the condition to the existing relation.
-
1
def where!(opts, *rest)
-
references!(PredicateBuilder.references(opts)) if Hash === opts
-
-
self.where_values += build_where(opts, rest)
-
self
-
end
-
-
# Allows to specify a HAVING clause. Note that you can't use HAVING
-
# without also specifying a GROUP clause.
-
#
-
# Order.having('SUM(price) > 30').group('user_id')
-
1
def having(opts, *rest)
-
opts.blank? ? self : spawn.having!(opts, *rest)
-
end
-
-
# Like #having, but modifies relation in place.
-
1
def having!(opts, *rest)
-
references!(PredicateBuilder.references(opts)) if Hash === opts
-
-
self.having_values += build_where(opts, rest)
-
self
-
end
-
-
# Specifies a limit for the number of records to retrieve.
-
#
-
# User.limit(10) # generated SQL has 'LIMIT 10'
-
#
-
# User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
-
1
def limit(value)
-
spawn.limit!(value)
-
end
-
-
# Like #limit, but modifies relation in place.
-
1
def limit!(value)
-
self.limit_value = value
-
self
-
end
-
-
# Specifies the number of rows to skip before returning rows.
-
#
-
# User.offset(10) # generated SQL has "OFFSET 10"
-
#
-
# Should be used with order.
-
#
-
# User.offset(10).order("name ASC")
-
1
def offset(value)
-
spawn.offset!(value)
-
end
-
-
# Like #offset, but modifies relation in place.
-
1
def offset!(value)
-
self.offset_value = value
-
self
-
end
-
-
# Specifies locking settings (default to +true+). For more information
-
# on locking, please see +ActiveRecord::Locking+.
-
1
def lock(locks = true)
-
spawn.lock!(locks)
-
end
-
-
# Like #lock, but modifies relation in place.
-
1
def lock!(locks = true)
-
case locks
-
when String, TrueClass, NilClass
-
self.lock_value = locks || true
-
else
-
self.lock_value = false
-
end
-
-
self
-
end
-
-
# Returns a chainable relation with zero records, specifically an
-
# instance of the <tt>ActiveRecord::NullRelation</tt> class.
-
#
-
# The returned <tt>ActiveRecord::NullRelation</tt> inherits from Relation and implements the
-
# Null Object pattern. It is an object with defined null behavior and always returns an empty
-
# array of records without quering the database.
-
#
-
# Any subsequent condition chained to the returned relation will continue
-
# generating an empty relation and will not fire any query to the database.
-
#
-
# Used in cases where a method or scope could return zero records but the
-
# result needs to be chainable.
-
#
-
# For example:
-
#
-
# @posts = current_user.visible_posts.where(:name => params[:name])
-
# # => the visible_posts method is expected to return a chainable Relation
-
#
-
# def visible_posts
-
# case role
-
# when 'Country Manager'
-
# Post.where(:country => country)
-
# when 'Reviewer'
-
# Post.published
-
# when 'Bad User'
-
# Post.none # => returning [] instead breaks the previous code
-
# end
-
# end
-
#
-
1
def none
-
extending(NullRelation)
-
end
-
-
# Like #none, but modifies relation in place.
-
1
def none!
-
extending!(NullRelation)
-
end
-
-
# Sets readonly attributes for the returned relation. If value is
-
# true (default), attempting to update a record will result in an error.
-
#
-
# users = User.readonly
-
# users.first.save
-
# => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
-
1
def readonly(value = true)
-
spawn.readonly!(value)
-
end
-
-
# Like #readonly, but modifies relation in place.
-
1
def readonly!(value = true)
-
self.readonly_value = value
-
self
-
end
-
-
# Sets attributes to be used when creating new records from a
-
# relation object.
-
#
-
# users = User.where(name: 'Oscar')
-
# users.new.name # => 'Oscar'
-
#
-
# users = users.create_with(name: 'DHH')
-
# users.new.name # => 'DHH'
-
#
-
# You can pass +nil+ to +create_with+ to reset attributes:
-
#
-
# users = users.create_with(nil)
-
# users.new.name # => 'Oscar'
-
1
def create_with(value)
-
spawn.create_with!(value)
-
end
-
-
# Like #create_with but modifies the relation in place. Raises
-
# +ImmutableRelation+ if the relation has already been loaded.
-
#
-
# users = User.all.create_with!(name: 'Oscar')
-
# users.new.name # => 'Oscar'
-
1
def create_with!(value)
-
self.create_with_value = value ? create_with_value.merge(value) : {}
-
self
-
end
-
-
# Specifies table from which the records will be fetched. For example:
-
#
-
# Topic.select('title').from('posts')
-
# #=> SELECT title FROM posts
-
#
-
# Can accept other relation objects. For example:
-
#
-
# Topic.select('title').from(Topic.approved)
-
# # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
-
#
-
# Topic.select('a.title').from(Topic.approved, :a)
-
# # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
-
#
-
1
def from(value, subquery_name = nil)
-
spawn.from!(value, subquery_name)
-
end
-
-
# Like #from, but modifies relation in place.
-
1
def from!(value, subquery_name = nil)
-
self.from_value = [value, subquery_name]
-
self
-
end
-
-
# Specifies whether the records should be unique or not. For example:
-
#
-
# User.select(:name)
-
# # => Might return two records with the same name
-
#
-
# User.select(:name).uniq
-
# # => Returns 1 record per unique name
-
#
-
# User.select(:name).uniq.uniq(false)
-
# # => You can also remove the uniqueness
-
1
def uniq(value = true)
-
spawn.uniq!(value)
-
end
-
-
# Like #uniq, but modifies relation in place.
-
1
def uniq!(value = true)
-
self.uniq_value = value
-
self
-
end
-
-
# Used to extend a scope with additional methods, either through
-
# a module or through a block provided.
-
#
-
# The object returned is a relation, which can be further extended.
-
#
-
# === Using a module
-
#
-
# module Pagination
-
# def page(number)
-
# # pagination code goes here
-
# end
-
# end
-
#
-
# scope = Model.all.extending(Pagination)
-
# scope.page(params[:page])
-
#
-
# You can also pass a list of modules:
-
#
-
# scope = Model.all.extending(Pagination, SomethingElse)
-
#
-
# === Using a block
-
#
-
# scope = Model.all.extending do
-
# def page(number)
-
# # pagination code goes here
-
# end
-
# end
-
# scope.page(params[:page])
-
#
-
# You can also use a block and a module list:
-
#
-
# scope = Model.all.extending(Pagination) do
-
# def per_page(number)
-
# # pagination code goes here
-
# end
-
# end
-
1
def extending(*modules, &block)
-
if modules.any? || block
-
spawn.extending!(*modules, &block)
-
else
-
self
-
end
-
end
-
-
# Like #extending, but modifies relation in place.
-
1
def extending!(*modules, &block)
-
modules << Module.new(&block) if block_given?
-
-
self.extending_values += modules.flatten
-
extend(*extending_values) if extending_values.any?
-
-
self
-
end
-
-
# Reverse the existing order clause on the relation.
-
#
-
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
-
1
def reverse_order
-
spawn.reverse_order!
-
end
-
-
# Like #reverse_order, but modifies relation in place.
-
1
def reverse_order!
-
self.reverse_order_value = !reverse_order_value
-
self
-
end
-
-
# Returns the Arel object associated with the relation.
-
1
def arel
-
@arel ||= with_default_scope.build_arel
-
end
-
-
# Like #arel, but ignores the default scope of the model.
-
1
def build_arel
-
arel = Arel::SelectManager.new(table.engine, table)
-
-
build_joins(arel, joins_values) unless joins_values.empty?
-
-
collapse_wheres(arel, (where_values - ['']).uniq)
-
-
arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?
-
-
arel.take(connection.sanitize_limit(limit_value)) if limit_value
-
arel.skip(offset_value.to_i) if offset_value
-
-
arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
-
-
build_order(arel)
-
-
build_select(arel, select_values.uniq)
-
-
arel.distinct(uniq_value)
-
arel.from(build_from) if from_value
-
arel.lock(lock_value) if lock_value
-
-
arel
-
end
-
-
1
private
-
-
1
def custom_join_ast(table, joins)
-
joins = joins.reject { |join| join.blank? }
-
-
return [] if joins.empty?
-
-
@implicit_readonly = true
-
-
joins.map do |join|
-
case join
-
when Array
-
join = Arel.sql(join.join(' ')) if array_of_strings?(join)
-
when String
-
join = Arel.sql(join)
-
end
-
table.create_string_join(join)
-
end
-
end
-
-
1
def collapse_wheres(arel, wheres)
-
equalities = wheres.grep(Arel::Nodes::Equality)
-
-
arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
-
-
(wheres - equalities).each do |where|
-
where = Arel.sql(where) if String === where
-
arel.where(Arel::Nodes::Grouping.new(where))
-
end
-
end
-
-
1
def build_where(opts, other = [])
-
case opts
-
when String, Array
-
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
-
when Hash
-
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
-
PredicateBuilder.build_from_hash(klass, attributes, table)
-
else
-
[opts]
-
end
-
end
-
-
1
def build_from
-
opts, name = from_value
-
case opts
-
when Relation
-
name ||= 'subquery'
-
opts.arel.as(name.to_s)
-
else
-
opts
-
end
-
end
-
-
1
def build_joins(manager, joins)
-
buckets = joins.group_by do |join|
-
case join
-
when String
-
:string_join
-
when Hash, Symbol, Array
-
:association_join
-
when ActiveRecord::Associations::JoinDependency::JoinAssociation
-
:stashed_join
-
when Arel::Nodes::Join
-
:join_node
-
else
-
raise 'unknown class: %s' % join.class.name
-
end
-
end
-
-
association_joins = buckets[:association_join] || []
-
stashed_association_joins = buckets[:stashed_join] || []
-
join_nodes = (buckets[:join_node] || []).uniq
-
string_joins = (buckets[:string_join] || []).map { |x|
-
x.strip
-
}.uniq
-
-
join_list = join_nodes + custom_join_ast(manager, string_joins)
-
-
join_dependency = ActiveRecord::Associations::JoinDependency.new(
-
@klass,
-
association_joins,
-
join_list
-
)
-
-
join_dependency.graft(*stashed_association_joins)
-
-
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
-
-
# FIXME: refactor this to build an AST
-
join_dependency.join_associations.each do |association|
-
association.join_to(manager)
-
end
-
-
manager.join_sources.concat join_list
-
-
manager
-
end
-
-
1
def build_select(arel, selects)
-
unless selects.empty?
-
@implicit_readonly = false
-
arel.project(*selects)
-
else
-
arel.project(@klass.arel_table[Arel.star])
-
end
-
end
-
-
1
def reverse_sql_order(order_query)
-
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
-
-
order_query.flat_map do |o|
-
case o
-
when Arel::Nodes::Ordering
-
o.reverse
-
when String
-
o.to_s.split(',').collect do |s|
-
s.strip!
-
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
-
end
-
when Symbol
-
{ o => :desc }
-
when Hash
-
o.each_with_object({}) do |(field, dir), memo|
-
memo[field] = (dir == :asc ? :desc : :asc )
-
end
-
else
-
o
-
end
-
end
-
end
-
-
1
def array_of_strings?(o)
-
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
-
end
-
-
1
def build_order(arel)
-
orders = order_values
-
orders = reverse_sql_order(orders) if reverse_order_value
-
-
orders = orders.uniq.reject(&:blank?).flat_map do |order|
-
case order
-
when Symbol
-
table[order].asc
-
when Hash
-
order.map { |field, dir| table[field].send(dir) }
-
else
-
order
-
end
-
end
-
-
arel.order(*orders) unless orders.empty?
-
end
-
-
1
def validate_order_args(args)
-
args.select { |a| Hash === a }.each do |h|
-
unless (h.values - [:asc, :desc]).empty?
-
raise ArgumentError, 'Direction should be :asc or :desc'
-
end
-
end
-
end
-
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_record/relation/merger'
-
-
1
module ActiveRecord
-
1
module SpawnMethods
-
-
# This is overridden by Associations::CollectionProxy
-
1
def spawn #:nodoc:
-
clone
-
end
-
-
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
-
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
-
#
-
# ==== Examples
-
#
-
# Post.where(:published => true).joins(:comments).merge( Comment.where(:spam => false) )
-
# # Performs a single join query with both where conditions.
-
#
-
# recent_posts = Post.order('created_at DESC').first(5)
-
# Post.where(:published => true).merge(recent_posts)
-
# # Returns the intersection of all published posts with the 5 most recently created posts.
-
# # (This is just an example. You'd probably want to do this with a single query!)
-
#
-
# Procs will be evaluated by merge:
-
#
-
# Post.where(published: true).merge(-> { joins(:comments) })
-
# # => Post.where(published: true).joins(:comments)
-
#
-
# This is mainly intended for sharing common conditions between multiple associations.
-
#
-
1
def merge(other)
-
if other.is_a?(Array)
-
to_a & other
-
elsif other
-
spawn.merge!(other)
-
else
-
self
-
end
-
end
-
-
# Like #merge, but applies changes in place.
-
1
def merge!(other)
-
if !other.is_a?(Relation) && other.respond_to?(:to_proc)
-
instance_exec(&other)
-
else
-
klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
-
klass.new(self, other).merge
-
end
-
end
-
-
# Removes from the query the condition(s) specified in +skips+.
-
#
-
# Example:
-
#
-
# Post.order('id asc').except(:order) # discards the order condition
-
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
-
#
-
1
def except(*skips)
-
result = Relation.new(klass, table, values.except(*skips))
-
result.default_scoped = default_scoped
-
result.extend(*extending_values) if extending_values.any?
-
result
-
end
-
-
# Removes any condition from the query other than the one(s) specified in +onlies+.
-
#
-
# Example:
-
#
-
# Post.order('id asc').only(:where) # discards the order condition
-
# Post.order('id asc').only(:where, :order) # uses the specified order
-
#
-
1
def only(*onlies)
-
result = Relation.new(klass, table, values.slice(*onlies))
-
result.default_scoped = default_scoped
-
result.extend(*extending_values) if extending_values.any?
-
result
-
end
-
-
end
-
end
-
1
module ActiveRecord
-
1
module Sanitization
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
def quote_value(value, column = nil) #:nodoc:
-
connection.quote(value,column)
-
end
-
-
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
-
1
def sanitize(object) #:nodoc:
-
connection.quote(object)
-
end
-
-
1
protected
-
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
-
# them into a valid SQL fragment for a WHERE clause.
-
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
-
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
-
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
-
1
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
-
return nil if condition.blank?
-
-
case condition
-
when Array; sanitize_sql_array(condition)
-
when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
-
else condition
-
end
-
end
-
1
alias_method :sanitize_sql, :sanitize_sql_for_conditions
-
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
-
# them into a valid SQL fragment for a SET clause.
-
# { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
-
1
def sanitize_sql_for_assignment(assignments)
-
case assignments
-
when Array; sanitize_sql_array(assignments)
-
when Hash; sanitize_sql_hash_for_assignment(assignments)
-
else assignments
-
end
-
end
-
-
# Accepts a hash of SQL conditions and replaces those attributes
-
# that correspond to a +composed_of+ relationship with their expanded
-
# aggregate attribute values.
-
# Given:
-
# class Person < ActiveRecord::Base
-
# composed_of :address, :class_name => "Address",
-
# :mapping => [%w(address_street street), %w(address_city city)]
-
# end
-
# Then:
-
# { :address => Address.new("813 abc st.", "chicago") }
-
# # => { :address_street => "813 abc st.", :address_city => "chicago" }
-
1
def expand_hash_conditions_for_aggregates(attrs)
-
expanded_attrs = {}
-
attrs.each do |attr, value|
-
if aggregation = reflect_on_aggregation(attr.to_sym)
-
mapping = aggregation.mapping
-
mapping.each do |field_attr, aggregate_attr|
-
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
-
expanded_attrs[field_attr] = value
-
else
-
expanded_attrs[field_attr] = value.send(aggregate_attr)
-
end
-
end
-
else
-
expanded_attrs[attr] = value
-
end
-
end
-
expanded_attrs
-
end
-
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
-
# { :name => "foo'bar", :group_id => 4 }
-
# # => "name='foo''bar' and group_id= 4"
-
# { :status => nil, :group_id => [1,2,3] }
-
# # => "status IS NULL and group_id IN (1,2,3)"
-
# { :age => 13..18 }
-
# # => "age BETWEEN 13 AND 18"
-
# { 'other_records.id' => 7 }
-
# # => "`other_records`.`id` = 7"
-
# { :other_records => { :id => 7 } }
-
# # => "`other_records`.`id` = 7"
-
# And for value objects on a composed_of relationship:
-
# { :address => Address.new("123 abc st.", "chicago") }
-
# # => "address_street='123 abc st.' and address_city='chicago'"
-
1
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
-
attrs = expand_hash_conditions_for_aggregates(attrs)
-
-
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
-
PredicateBuilder.build_from_hash(self.class, attrs, table).map { |b|
-
connection.visitor.accept b
-
}.join(' AND ')
-
end
-
1
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
-
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
-
# { :status => nil, :group_id => 1 }
-
# # => "status = NULL , group_id = 1"
-
1
def sanitize_sql_hash_for_assignment(attrs)
-
attrs.map do |attr, value|
-
"#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
-
end.join(', ')
-
end
-
-
# Accepts an array of conditions. The array has each value
-
# sanitized and interpolated into the SQL statement.
-
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
-
1
def sanitize_sql_array(ary)
-
statement, *values = ary
-
if values.first.is_a?(Hash) && statement =~ /:\w+/
-
replace_named_bind_variables(statement, values.first)
-
elsif statement.include?('?')
-
replace_bind_variables(statement, values)
-
elsif statement.blank?
-
statement
-
else
-
statement % values.collect { |value| connection.quote_string(value.to_s) }
-
end
-
end
-
-
1
alias_method :sanitize_conditions, :sanitize_sql
-
-
1
def replace_bind_variables(statement, values) #:nodoc:
-
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
-
bound = values.dup
-
c = connection
-
statement.gsub('?') { quote_bound_value(bound.shift, c) }
-
end
-
-
1
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
-
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
-
if $1 == ':' # skip postgresql casts
-
$& # return the whole match
-
elsif bind_vars.include?(match = $2.to_sym)
-
quote_bound_value(bind_vars[match])
-
else
-
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
-
end
-
end
-
end
-
-
1
def quote_bound_value(value, c = connection) #:nodoc:
-
if value.respond_to?(:map) && !value.acts_like?(:string)
-
if value.respond_to?(:empty?) && value.empty?
-
c.quote(nil)
-
else
-
value.map { |v| c.quote(v) }.join(',')
-
end
-
else
-
c.quote(value)
-
end
-
end
-
-
1
def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
-
unless expected == provided
-
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
-
end
-
end
-
end
-
-
# TODO: Deprecate this
-
1
def quoted_id
-
self.class.quote_value(id, column_for_attribute(self.class.primary_key))
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module Scoping
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
include Default
-
1
include Named
-
end
-
-
1
module ClassMethods
-
1
def current_scope #:nodoc:
-
Thread.current["#{self}_current_scope"]
-
end
-
-
1
def current_scope=(scope) #:nodoc:
-
Thread.current["#{self}_current_scope"] = scope
-
end
-
end
-
-
1
def populate_with_current_scope_attributes
-
return unless self.class.scope_attributes?
-
-
self.class.scope_attributes.each do |att,value|
-
send("#{att}=", value) if respond_to?("#{att}=")
-
end
-
end
-
-
end
-
end
-
1
require 'active_support/core_ext/array'
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
-
1
module ActiveRecord
-
# = Active Record \Named \Scopes
-
1
module Scoping
-
1
module Named
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Returns an <tt>ActiveRecord::Relation</tt> scope object.
-
#
-
# posts = Post.all
-
# posts.size # Fires "select count(*) from posts" and returns the count
-
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
-
#
-
# fruits = Fruit.all
-
# fruits = fruits.where(color: 'red') if options[:red_only]
-
# fruits = fruits.limit(10) if limited?
-
#
-
# You can define a scope that applies to all finders using
-
# <tt>ActiveRecord::Base.default_scope</tt>.
-
1
def all
-
if current_scope
-
current_scope.clone
-
else
-
scope = relation
-
scope.default_scoped = true
-
scope
-
end
-
end
-
-
# Collects attributes from scopes that should be applied when creating
-
# an AR instance for the particular class this is called on.
-
1
def scope_attributes # :nodoc:
-
if current_scope
-
current_scope.scope_for_create
-
else
-
scope = relation
-
scope.default_scoped = true
-
scope.scope_for_create
-
end
-
end
-
-
# Are there default attributes associated with this scope?
-
1
def scope_attributes? # :nodoc:
-
current_scope || default_scopes.any?
-
end
-
-
# Adds a class method for retrieving and querying objects. A \scope
-
# represents a narrowing of a database query, such as
-
# <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
-
#
-
# class Shirt < ActiveRecord::Base
-
# scope :red, -> { where(color: 'red') }
-
# scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
-
# end
-
#
-
# The above calls to +scope+ define class methods <tt>Shirt.red</tt> and
-
# <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
-
# represents the query <tt>Shirt.where(color: 'red')</tt>.
-
#
-
# You should always pass a callable object to the scopes defined
-
# with +scope+. This ensures that the scope is re-evaluated each
-
# time it is called.
-
#
-
# Note that this is simply 'syntactic sugar' for defining an actual
-
# class method:
-
#
-
# class Shirt < ActiveRecord::Base
-
# def self.red
-
# where(color: 'red')
-
# end
-
# end
-
#
-
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
-
# <tt>Shirt.red</tt> is not an Array; it resembles the association object
-
# constructed by a +has_many+ declaration. For instance, you can invoke
-
# <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
-
# <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
-
# association objects, named \scopes act like an Array, implementing
-
# Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
-
# and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
-
# <tt>Shirt.red</tt> really was an Array.
-
#
-
# These named \scopes are composable. For instance,
-
# <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
-
# both red and dry clean only. Nested finds and calculations also work
-
# with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
-
# returns the number of garments for which these criteria obtain.
-
# Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
-
#
-
# All scopes are available as class methods on the ActiveRecord::Base
-
# descendant upon which the \scopes were defined. But they are also
-
# available to +has_many+ associations. If,
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :shirts
-
# end
-
#
-
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
-
# Elton's red, dry clean only shirts.
-
#
-
# \Named scopes can also have extensions, just as with +has_many+
-
# declarations:
-
#
-
# class Shirt < ActiveRecord::Base
-
# scope :red, -> { where(color: 'red') } do
-
# def dom_id
-
# 'red_shirts'
-
# end
-
# end
-
# end
-
#
-
# Scopes can also be used while creating/building a record.
-
#
-
# class Article < ActiveRecord::Base
-
# scope :published, -> { where(published: true) }
-
# end
-
#
-
# Article.published.new.published # => true
-
# Article.published.create.published # => true
-
#
-
# \Class methods on your model are automatically available
-
# on scopes. Assuming the following setup:
-
#
-
# class Article < ActiveRecord::Base
-
# scope :published, -> { where(published: true) }
-
# scope :featured, -> { where(featured: true) }
-
#
-
# def self.latest_article
-
# order('published_at desc').first
-
# end
-
#
-
# def self.titles
-
# map(&:title)
-
# end
-
#
-
# end
-
#
-
# We are able to call the methods like this:
-
#
-
# Article.published.featured.latest_article
-
# Article.featured.titles
-
-
1
def scope(name, body, &block)
-
1
extension = Module.new(&block) if block
-
-
# Check body.is_a?(Relation) to prevent the relation actually being
-
# loaded by respond_to?
-
1
if body.is_a?(Relation) || !body.respond_to?(:call)
-
ActiveSupport::Deprecation.warn(
-
"Using #scope without passing a callable object is deprecated. For " \
-
"example `scope :red, where(color: 'red')` should be changed to " \
-
"`scope :red, -> { where(color: 'red') }`. There are numerous gotchas " \
-
"in the former usage and it makes the implementation more complicated " \
-
"and buggy. (If you prefer, you can just define a class method named " \
-
"`self.red`.)"
-
)
-
end
-
-
1
singleton_class.send(:define_method, name) do |*args|
-
options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body
-
relation = all.merge(options)
-
-
extension ? relation.extending(extension) : relation
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord #:nodoc:
-
# = Active Record Serialization
-
1
module Serialization
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Serializers::JSON
-
-
1
included do
-
1
self.include_root_in_json = true
-
end
-
-
1
def serializable_hash(options = nil)
-
options = options.try(:clone) || {}
-
-
options[:except] = Array(options[:except]).map { |n| n.to_s }
-
options[:except] |= Array(self.class.inheritance_column)
-
-
super(options)
-
end
-
end
-
end
-
-
1
require 'active_record/serializers/xml_serializer'
-
1
require 'active_support/core_ext/hash/conversions'
-
-
1
module ActiveRecord #:nodoc:
-
1
module Serialization
-
1
include ActiveModel::Serializers::Xml
-
-
# Builds an XML document to represent the model. Some configuration is
-
# available through +options+. However more complicated cases should
-
# override ActiveRecord::Base#to_xml.
-
#
-
# By default the generated XML document will include the processing
-
# instruction and all the object's attributes. For example:
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <topic>
-
# <title>The First Topic</title>
-
# <author-name>David</author-name>
-
# <id type="integer">1</id>
-
# <approved type="boolean">false</approved>
-
# <replies-count type="integer">0</replies-count>
-
# <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time>
-
# <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on>
-
# <content>Have a nice day</content>
-
# <author-email-address>david@loudthinking.com</author-email-address>
-
# <parent-id></parent-id>
-
# <last-read type="date">2004-04-15</last-read>
-
# </topic>
-
#
-
# This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
-
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
-
# The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
-
# +attributes+ method. The default is to dasherize all column names, but you
-
# can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
-
# to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
-
# To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
-
#
-
# For instance:
-
#
-
# topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
-
#
-
# <topic>
-
# <title>The First Topic</title>
-
# <author-name>David</author-name>
-
# <approved type="boolean">false</approved>
-
# <content>Have a nice day</content>
-
# <author-email-address>david@loudthinking.com</author-email-address>
-
# <parent-id></parent-id>
-
# <last-read type="date">2004-04-15</last-read>
-
# </topic>
-
#
-
# To include first level associations use <tt>:include</tt>:
-
#
-
# firm.to_xml :include => [ :account, :clients ]
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <firm>
-
# <id type="integer">1</id>
-
# <rating type="integer">1</rating>
-
# <name>37signals</name>
-
# <clients type="array">
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Summit</name>
-
# </client>
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Microsoft</name>
-
# </client>
-
# </clients>
-
# <account>
-
# <id type="integer">1</id>
-
# <credit-limit type="integer">50</credit-limit>
-
# </account>
-
# </firm>
-
#
-
# Additionally, the record being serialized will be passed to a Proc's second
-
# parameter. This allows for ad hoc additions to the resultant document that
-
# incorporate the context of the record being serialized. And by leveraging the
-
# closure created by a Proc, to_xml can be used to add elements that normally fall
-
# outside of the scope of the model -- for example, generating and appending URLs
-
# associated with models.
-
#
-
# proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
-
# firm.to_xml :procs => [ proc ]
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <name-reverse>slangis73</name-reverse>
-
# </firm>
-
#
-
# To include deeper levels of associations pass a hash like this:
-
#
-
# firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <firm>
-
# <id type="integer">1</id>
-
# <rating type="integer">1</rating>
-
# <name>37signals</name>
-
# <clients type="array">
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Summit</name>
-
# <address>
-
# ...
-
# </address>
-
# </client>
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Microsoft</name>
-
# <address>
-
# ...
-
# </address>
-
# </client>
-
# </clients>
-
# <account>
-
# <id type="integer">1</id>
-
# <credit-limit type="integer">50</credit-limit>
-
# </account>
-
# </firm>
-
#
-
# To include any methods on the model being called use <tt>:methods</tt>:
-
#
-
# firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <calculated-earnings>100000000000000000</calculated-earnings>
-
# <real-earnings>5</real-earnings>
-
# </firm>
-
#
-
# To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
-
# modified version of the options hash that was given to +to_xml+:
-
#
-
# proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
-
# firm.to_xml :procs => [ proc ]
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <abc>def</abc>
-
# </firm>
-
#
-
# Alternatively, you can yield the builder object as part of the +to_xml+ call:
-
#
-
# firm.to_xml do |xml|
-
# xml.creator do
-
# xml.first_name "David"
-
# xml.last_name "Heinemeier Hansson"
-
# end
-
# end
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <creator>
-
# <first_name>David</first_name>
-
# <last_name>Heinemeier Hansson</last_name>
-
# </creator>
-
# </firm>
-
#
-
# As noted above, you may override +to_xml+ in your ActiveRecord::Base
-
# subclasses to have complete control about what's generated. The general
-
# form of doing this is:
-
#
-
# class IHaveMyOwnXML < ActiveRecord::Base
-
# def to_xml(options = {})
-
# require 'builder'
-
# options[:indent] ||= 2
-
# xml = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
-
# xml.instruct! unless options[:skip_instruct]
-
# xml.level_one do
-
# xml.tag!(:second_level, 'content')
-
# end
-
# end
-
# end
-
1
def to_xml(options = {}, &block)
-
XmlSerializer.new(self, options).serialize(&block)
-
end
-
end
-
-
1
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
-
1
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
-
1
def compute_type
-
klass = @serializable.class
-
type = if klass.serialized_attributes.key?(name)
-
super
-
elsif klass.columns_hash.key?(name)
-
klass.columns_hash[name].type
-
else
-
NilClass
-
end
-
-
{ :text => :string,
-
:time => :datetime }[type] || type
-
end
-
1
protected :compute_type
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActiveRecord
-
# Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
-
# It's like a simple key/value store baked into your record when you don't care about being able to
-
# query that store outside the context of a single record.
-
#
-
# You can then declare accessors to this store that are then accessible just like any other attribute
-
# of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
-
# already built around just accessing attributes on the model.
-
#
-
# Make sure that you declare the database column used for the serialized store as a text, so there's
-
# plenty of room.
-
#
-
# You can set custom coder to encode/decode your serialized attributes to/from different formats.
-
# JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
-
#
-
# Examples:
-
#
-
# class User < ActiveRecord::Base
-
# store :settings, accessors: [ :color, :homepage ], coder: JSON
-
# end
-
#
-
# u = User.new(color: 'black', homepage: '37signals.com')
-
# u.color # Accessor stored attribute
-
# u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
-
#
-
# # There is no difference between strings and symbols for accessing custom attributes
-
# u.settings[:country] # => 'Denmark'
-
# u.settings['country'] # => 'Denmark'
-
#
-
# # Add additional accessors to an existing store through store_accessor
-
# class SuperUser < User
-
# store_accessor :settings, :privileges, :servants
-
# end
-
#
-
# The stored attribute names can be retrieved using +stored_attributes+.
-
#
-
# User.stored_attributes[:settings] # [:color, :homepage]
-
#
-
# == Overwriting default accessors
-
#
-
# All stored values are automatically available through accessors on the Active Record
-
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
-
# the default accessors (using the same name as the attribute) and calling
-
# <tt>read_store_attribute(store_attribute_name, attr_name)</tt> and
-
# <tt>write_store_attribute(store_attribute_name, attr_name, value)</tt> to actually
-
# change things.
-
#
-
# class Song < ActiveRecord::Base
-
# # Uses a stored integer to hold the volume adjustment of the song
-
# store :settings, accessors: [:volume_adjustment]
-
#
-
# def volume_adjustment=(decibels)
-
# write_store_attribute(:settings, :volume_adjustment, decibels.to_i)
-
# end
-
#
-
# def volume_adjustment
-
# read_store_attribute(:settings, :volume_adjustment).to_i
-
# end
-
# end
-
1
module Store
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :stored_attributes, instance_accessor: false
-
1
self.stored_attributes = {}
-
end
-
-
1
module ClassMethods
-
1
def store(store_attribute, options = {})
-
serialize store_attribute, IndifferentCoder.new(options[:coder])
-
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
-
end
-
-
1
def store_accessor(store_attribute, *keys)
-
keys = keys.flatten
-
keys.each do |key|
-
define_method("#{key}=") do |value|
-
write_store_attribute(store_attribute, key, value)
-
end
-
-
define_method(key) do
-
read_store_attribute(store_attribute, key)
-
end
-
end
-
-
self.stored_attributes[store_attribute] ||= []
-
self.stored_attributes[store_attribute] |= keys
-
end
-
end
-
-
1
protected
-
1
def read_store_attribute(store_attribute, key)
-
attribute = initialize_store_attribute(store_attribute)
-
attribute[key]
-
end
-
-
1
def write_store_attribute(store_attribute, key, value)
-
attribute = initialize_store_attribute(store_attribute)
-
if value != attribute[key]
-
send :"#{store_attribute}_will_change!"
-
attribute[key] = value
-
end
-
end
-
-
1
private
-
1
def initialize_store_attribute(store_attribute)
-
attribute = send(store_attribute)
-
unless attribute.is_a?(HashWithIndifferentAccess)
-
attribute = IndifferentCoder.as_indifferent_hash(attribute)
-
send :"#{store_attribute}=", attribute
-
end
-
attribute
-
end
-
-
1
class IndifferentCoder # :nodoc:
-
1
def initialize(coder_or_class_name)
-
@coder =
-
if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
-
coder_or_class_name
-
else
-
ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object)
-
end
-
end
-
-
1
def dump(obj)
-
@coder.dump self.class.as_indifferent_hash(obj)
-
end
-
-
1
def load(yaml)
-
self.class.as_indifferent_hash @coder.load(yaml)
-
end
-
-
1
def self.as_indifferent_hash(obj)
-
case obj
-
when HashWithIndifferentAccess
-
obj
-
when Hash
-
obj.with_indifferent_access
-
else
-
HashWithIndifferentAccess.new
-
end
-
end
-
end
-
end
-
end
-
1
require 'thread'
-
-
1
module ActiveRecord
-
# See ActiveRecord::Transactions::ClassMethods for documentation.
-
1
module Transactions
-
1
extend ActiveSupport::Concern
-
-
1
class TransactionError < ActiveRecordError # :nodoc:
-
end
-
-
1
included do
-
1
define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
-
end
-
-
# = Active Record Transactions
-
#
-
# Transactions are protective blocks where SQL statements are only permanent
-
# if they can all succeed as one atomic action. The classic example is a
-
# transfer between two accounts where you can only have a deposit if the
-
# withdrawal succeeded and vice versa. Transactions enforce the integrity of
-
# the database and guard the data against program errors or database
-
# break-downs. So basically you should use transaction blocks whenever you
-
# have a number of statements that must be executed together or not at all.
-
#
-
# For example:
-
#
-
# ActiveRecord::Base.transaction do
-
# david.withdrawal(100)
-
# mary.deposit(100)
-
# end
-
#
-
# This example will only take money from David and give it to Mary if neither
-
# +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a
-
# ROLLBACK that returns the database to the state before the transaction
-
# began. Be aware, though, that the objects will _not_ have their instance
-
# data returned to their pre-transactional state.
-
#
-
# == Different Active Record classes in a single transaction
-
#
-
# Though the transaction class method is called on some Active Record class,
-
# the objects within the transaction block need not all be instances of
-
# that class. This is because transactions are per-database connection, not
-
# per-model.
-
#
-
# In this example a +balance+ record is transactionally saved even
-
# though +transaction+ is called on the +Account+ class:
-
#
-
# Account.transaction do
-
# balance.save!
-
# account.save!
-
# end
-
#
-
# The +transaction+ method is also available as a model instance method.
-
# For example, you can also do this:
-
#
-
# balance.transaction do
-
# balance.save!
-
# account.save!
-
# end
-
#
-
# == Transactions are not distributed across database connections
-
#
-
# A transaction acts on a single database connection. If you have
-
# multiple class-specific databases, the transaction will not protect
-
# interaction among them. One workaround is to begin a transaction
-
# on each class whose models you alter:
-
#
-
# Student.transaction do
-
# Course.transaction do
-
# course.enroll(student)
-
# student.units += course.units
-
# end
-
# end
-
#
-
# This is a poor solution, but fully distributed transactions are beyond
-
# the scope of Active Record.
-
#
-
# == +save+ and +destroy+ are automatically wrapped in a transaction
-
#
-
# Both +save+ and +destroy+ come wrapped in a transaction that ensures
-
# that whatever you do in validations or callbacks will happen under its
-
# protected cover. So you can use validations to check for values that
-
# the transaction depends on or you can raise exceptions in the callbacks
-
# to rollback, including <tt>after_*</tt> callbacks.
-
#
-
# As a consequence changes to the database are not seen outside your connection
-
# until the operation is complete. For example, if you try to update the index
-
# of a search engine in +after_save+ the indexer won't see the updated record.
-
# The +after_commit+ callback is the only one that is triggered once the update
-
# is committed. See below.
-
#
-
# == Exception handling and rolling back
-
#
-
# Also have in mind that exceptions thrown within a transaction block will
-
# be propagated (after triggering the ROLLBACK), so you should be ready to
-
# catch those in your application code.
-
#
-
# One exception is the <tt>ActiveRecord::Rollback</tt> exception, which will trigger
-
# a ROLLBACK when raised, but not be re-raised by the transaction block.
-
#
-
# *Warning*: one should not catch <tt>ActiveRecord::StatementInvalid</tt> exceptions
-
# inside a transaction block. <tt>ActiveRecord::StatementInvalid</tt> exceptions indicate that an
-
# error occurred at the database level, for example when a unique constraint
-
# is violated. On some database systems, such as PostgreSQL, database errors
-
# inside a transaction cause the entire transaction to become unusable
-
# until it's restarted from the beginning. Here is an example which
-
# demonstrates the problem:
-
#
-
# # Suppose that we have a Number model with a unique column called 'i'.
-
# Number.transaction do
-
# Number.create(:i => 0)
-
# begin
-
# # This will raise a unique constraint error...
-
# Number.create(:i => 0)
-
# rescue ActiveRecord::StatementInvalid
-
# # ...which we ignore.
-
# end
-
#
-
# # On PostgreSQL, the transaction is now unusable. The following
-
# # statement will cause a PostgreSQL error, even though the unique
-
# # constraint is no longer violated:
-
# Number.create(:i => 1)
-
# # => "PGError: ERROR: current transaction is aborted, commands
-
# # ignored until end of transaction block"
-
# end
-
#
-
# One should restart the entire transaction if an
-
# <tt>ActiveRecord::StatementInvalid</tt> occurred.
-
#
-
# == Nested transactions
-
#
-
# +transaction+ calls can be nested. By default, this makes all database
-
# statements in the nested transaction block become part of the parent
-
# transaction. For example, the following behavior may be surprising:
-
#
-
# User.transaction do
-
# User.create(:username => 'Kotori')
-
# User.transaction do
-
# User.create(:username => 'Nemu')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# creates both "Kotori" and "Nemu". Reason is the <tt>ActiveRecord::Rollback</tt>
-
# exception in the nested block does not issue a ROLLBACK. Since these exceptions
-
# are captured in transaction blocks, the parent block does not see it and the
-
# real transaction is committed.
-
#
-
# In order to get a ROLLBACK for the nested transaction you may ask for a real
-
# sub-transaction by passing <tt>:requires_new => true</tt>. If anything goes wrong,
-
# the database rolls back to the beginning of the sub-transaction without rolling
-
# back the parent transaction. If we add it to the previous example:
-
#
-
# User.transaction do
-
# User.create(:username => 'Kotori')
-
# User.transaction(:requires_new => true) do
-
# User.create(:username => 'Nemu')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# only "Kotori" is created. (This works on MySQL and PostgreSQL, but not on SQLite3.)
-
#
-
# Most databases don't support true nested transactions. At the time of
-
# writing, the only database that we're aware of that supports true nested
-
# transactions, is MS-SQL. Because of this, Active Record emulates nested
-
# transactions by using savepoints on MySQL and PostgreSQL. See
-
# http://dev.mysql.com/doc/refman/5.6/en/savepoint.html
-
# for more information about savepoints.
-
#
-
# === Callbacks
-
#
-
# There are two types of callbacks associated with committing and rolling back transactions:
-
# +after_commit+ and +after_rollback+.
-
#
-
# +after_commit+ callbacks are called on every record saved or destroyed within a
-
# transaction immediately after the transaction is committed. +after_rollback+ callbacks
-
# are called on every record saved or destroyed within a transaction immediately after the
-
# transaction or savepoint is rolled back.
-
#
-
# These callbacks are useful for interacting with other systems since you will be guaranteed
-
# that the callback is only executed when the database is in a permanent state. For example,
-
# +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from
-
# within a transaction could trigger the cache to be regenerated before the database is updated.
-
#
-
# === Caveats
-
#
-
# If you're on MySQL, then do not use DDL operations in nested transactions
-
# blocks that are emulated with savepoints. That is, do not execute statements
-
# like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
-
# releases all savepoints upon executing a DDL operation. When +transaction+
-
# is finished and tries to release the savepoint it created earlier, a
-
# database error will occur because the savepoint has already been
-
# automatically released. The following example demonstrates the problem:
-
#
-
# Model.connection.transaction do # BEGIN
-
# Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
-
# Model.connection.create_table(...) # active_record_1 now automatically released
-
# end # RELEASE savepoint active_record_1
-
# # ^^^^ BOOM! database error!
-
# end
-
#
-
# Note that "TRUNCATE" is also a MySQL DDL statement!
-
1
module ClassMethods
-
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
-
1
def transaction(options = {}, &block)
-
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
-
connection.transaction(options, &block)
-
end
-
-
# This callback is called after a record has been created, updated, or destroyed.
-
#
-
# You can specify that the callback should only be fired by a certain action with
-
# the +:on+ option:
-
#
-
# after_commit :do_foo, :on => :create
-
# after_commit :do_bar, :on => :update
-
# after_commit :do_baz, :on => :destroy
-
#
-
# Also, to have the callback fired on create and update, but not on destroy:
-
#
-
# after_commit :do_zoo, :if => :persisted?
-
#
-
# Note that transactional fixtures do not play well with this feature. Please
-
# use the +test_after_commit+ gem to have these hooks fired in tests.
-
1
def after_commit(*args, &block)
-
options = args.last
-
if options.is_a?(Hash) && options[:on]
-
options[:if] = Array(options[:if])
-
options[:if] << "transaction_include_action?(:#{options[:on]})"
-
end
-
set_callback(:commit, :after, *args, &block)
-
end
-
-
# This callback is called after a create, update, or destroy are rolled back.
-
#
-
# Please check the documentation of +after_commit+ for options.
-
1
def after_rollback(*args, &block)
-
options = args.last
-
if options.is_a?(Hash) && options[:on]
-
options[:if] = Array(options[:if])
-
options[:if] << "transaction_include_action?(:#{options[:on]})"
-
end
-
set_callback(:rollback, :after, *args, &block)
-
end
-
end
-
-
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
-
1
def transaction(options = {}, &block)
-
self.class.transaction(options, &block)
-
end
-
-
1
def destroy #:nodoc:
-
with_transaction_returning_status { super }
-
end
-
-
1
def save(*) #:nodoc:
-
rollback_active_record_state! do
-
with_transaction_returning_status { super }
-
end
-
end
-
-
1
def save!(*) #:nodoc:
-
with_transaction_returning_status { super }
-
end
-
-
# Reset id and @new_record if the transaction rolls back.
-
1
def rollback_active_record_state!
-
remember_transaction_record_state
-
yield
-
rescue Exception
-
restore_transaction_record_state
-
raise
-
ensure
-
clear_transaction_record_state
-
end
-
-
# Call the after_commit callbacks
-
1
def committed! #:nodoc:
-
run_callbacks :commit
-
ensure
-
clear_transaction_record_state
-
end
-
-
# Call the after rollback callbacks. The restore_state argument indicates if the record
-
# state should be rolled back to the beginning or just to the last savepoint.
-
1
def rolledback!(force_restore_state = false) #:nodoc:
-
run_callbacks :rollback
-
ensure
-
restore_transaction_record_state(force_restore_state)
-
end
-
-
# Add the record to the current transaction so that the :after_rollback and :after_commit callbacks
-
# can be called.
-
1
def add_to_transaction
-
if self.class.connection.add_transaction_record(self)
-
remember_transaction_record_state
-
end
-
end
-
-
# Executes +method+ within a transaction and captures its return value as a
-
# status flag. If the status is true the transaction is committed, otherwise
-
# a ROLLBACK is issued. In any case the status flag is returned.
-
#
-
# This method is available within the context of an ActiveRecord::Base
-
# instance.
-
1
def with_transaction_returning_status
-
status = nil
-
self.class.transaction do
-
add_to_transaction
-
begin
-
status = yield
-
rescue ActiveRecord::Rollback
-
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
-
status = nil
-
end
-
-
raise ActiveRecord::Rollback unless status
-
end
-
status
-
end
-
-
1
protected
-
-
# Save the new record state and id of a record so it can be restored later if a transaction fails.
-
1
def remember_transaction_record_state #:nodoc:
-
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
-
@_start_transaction_state[:new_record] = @new_record
-
@_start_transaction_state[:destroyed] = @destroyed
-
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
-
@_start_transaction_state[:frozen?] = @attributes.frozen?
-
end
-
-
# Clear the new record state and id of a record.
-
1
def clear_transaction_record_state #:nodoc:
-
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
-
@_start_transaction_state.clear if @_start_transaction_state[:level] < 1
-
end
-
-
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
-
1
def restore_transaction_record_state(force = false) #:nodoc:
-
unless @_start_transaction_state.empty?
-
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
-
if @_start_transaction_state[:level] < 1 || force
-
restore_state = @_start_transaction_state
-
was_frozen = restore_state[:frozen?]
-
@attributes = @attributes.dup if @attributes.frozen?
-
@new_record = restore_state[:new_record]
-
@destroyed = restore_state[:destroyed]
-
if restore_state.has_key?(:id)
-
self.id = restore_state[:id]
-
else
-
@attributes.delete(self.class.primary_key)
-
@attributes_cache.delete(self.class.primary_key)
-
end
-
@attributes.freeze if was_frozen
-
@_start_transaction_state.clear
-
end
-
end
-
end
-
-
# Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
-
1
def transaction_record_state(state) #:nodoc:
-
@_start_transaction_state[state]
-
end
-
-
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
-
1
def transaction_include_action?(action) #:nodoc:
-
case action
-
when :create
-
transaction_record_state(:new_record)
-
when :destroy
-
destroyed?
-
when :update
-
!(transaction_record_state(:new_record) || destroyed?)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Translation
-
1
include ActiveModel::Translation
-
-
# Set the lookup ancestors for ActiveModel.
-
1
def lookup_ancestors #:nodoc:
-
klass = self
-
classes = [klass]
-
return classes if klass == ActiveRecord::Base
-
-
while klass != klass.base_class
-
classes << klass = klass.superclass
-
end
-
classes
-
end
-
-
# Set the i18n scope to overwrite ActiveModel.
-
1
def i18n_scope #:nodoc:
-
:activerecord
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record RecordInvalid
-
#
-
# Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
-
# +record+ method to retrieve the record which did not validate.
-
#
-
# begin
-
# complex_operation_that_calls_save!_internally
-
# rescue ActiveRecord::RecordInvalid => invalid
-
# puts invalid.record.errors
-
# end
-
1
class RecordInvalid < ActiveRecordError
-
1
attr_reader :record # :nodoc:
-
1
def initialize(record) # :nodoc:
-
@record = record
-
errors = @record.errors.full_messages.join(", ")
-
super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
-
end
-
end
-
-
# = Active Record Validations
-
#
-
# Active Record includes the majority of its validations from <tt>ActiveModel::Validations</tt>
-
# all of which accept the <tt>:on</tt> argument to define the context where the
-
# validations are active. Active Record will always supply either the context of
-
# <tt>:create</tt> or <tt>:update</tt> dependent on whether the model is a
-
# <tt>new_record?</tt>.
-
1
module Validations
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Validations
-
-
1
module ClassMethods
-
# Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
-
# so an exception is raised if the record is invalid.
-
1
def create!(attributes = nil, &block)
-
if attributes.is_a?(Array)
-
attributes.collect { |attr| create!(attr, &block) }
-
else
-
object = new(attributes)
-
yield(object) if block_given?
-
object.save!
-
object
-
end
-
end
-
end
-
-
# The validation process on save can be skipped by passing <tt>validate: false</tt>.
-
# The regular Base#save method is replaced with this when the validations
-
# module is mixed in, which it is by default.
-
1
def save(options={})
-
perform_validations(options) ? super : false
-
end
-
-
# Attempts to save the record just like Base#save but will raise a +RecordInvalid+
-
# exception instead of returning +false+ if the record is not valid.
-
1
def save!(options={})
-
perform_validations(options) ? super : raise(RecordInvalid.new(self))
-
end
-
-
# Runs all the validations within the specified context. Returns +true+ if
-
# no errors are found, +false+ otherwise.
-
#
-
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
-
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
-
#
-
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
-
# some <tt>:on</tt> option will only run in the specified context.
-
1
def valid?(context = nil)
-
context ||= (new_record? ? :create : :update)
-
output = super(context)
-
errors.empty? && output
-
end
-
-
1
protected
-
-
1
def perform_validations(options={}) # :nodoc:
-
perform_validation = options[:validate] != false
-
perform_validation ? valid?(options[:context]) : true
-
end
-
end
-
end
-
-
1
require "active_record/validations/associated"
-
1
require "active_record/validations/uniqueness"
-
1
require "active_record/validations/presence"
-
1
module ActiveRecord
-
1
module Validations
-
1
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
-
1
def validate_each(record, attribute, value)
-
if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?(record.validation_context) }.any?
-
record.errors.add(attribute, :invalid, options.merge(:value => value))
-
end
-
end
-
end
-
-
1
module ClassMethods
-
# Validates whether the associated object or objects are all valid
-
# themselves. Works with any kind of association.
-
#
-
# class Book < ActiveRecord::Base
-
# has_many :pages
-
# belongs_to :library
-
#
-
# validates_associated :pages, :library
-
# end
-
#
-
# WARNING: This validation must not be used on both ends of an association.
-
# Doing so will lead to a circular dependency and cause infinite recursion.
-
#
-
# NOTE: This validation will not fail if the association hasn't been
-
# assigned. If you want to ensure that the association is both present and
-
# guaranteed to be valid, you also need to use +validates_presence_of+.
-
#
-
# Configuration options:
-
#
-
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
-
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
-
# validation contexts by default (+nil+), other options are <tt>:create</tt>
-
# and <tt>:update</tt>.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
1
def validates_associated(*attr_names)
-
validates_with AssociatedValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Validations
-
1
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
-
1
def validate(record)
-
super
-
attributes.each do |attribute|
-
next unless record.class.reflect_on_association(attribute)
-
associated_records = Array(record.send(attribute))
-
-
# Superclass validates presence. Ensure present records aren't about to be destroyed.
-
if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
-
record.errors.add(attribute, :blank, options)
-
end
-
end
-
end
-
end
-
-
1
module ClassMethods
-
# Validates that the specified attributes are not blank (as defined by
-
# Object#blank?), and, if the attribute is an association, that the
-
# associated object is not marked for destruction. Happens by default
-
# on save.
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :face
-
# validates_presence_of :face
-
# end
-
#
-
# The face attribute must be in the object and it cannot be blank or marked
-
# for destruction.
-
#
-
# If you want to validate the presence of a boolean field (where the real values
-
# are true and false), you will want to use
-
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
-
#
-
# This is due to the way Object#blank? handles boolean values:
-
# <tt>false.blank? # => true</tt>.
-
#
-
# This validator defers to the ActiveModel validation for presence, adding the
-
# check to see that an associated object is not marked for destruction. This
-
# prevents the parent object from validating successfully and saving, which then
-
# deletes the associated object, thus putting the parent object into an invalid
-
# state.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
-
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
-
# validation contexts by default (+nil+), other options are <tt>:create</tt>
-
# and <tt>:update</tt>.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
-
# the validation should occur (e.g. <tt>if: :allow_validation</tt>, or
-
# <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
-
# or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:strict</tt> - Specifies whether validation should be strict.
-
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
-
1
def validates_presence_of(*attr_names)
-
2
validates_with PresenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/prepend_and_append'
-
-
1
module ActiveRecord
-
1
module Validations
-
1
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
-
1
def initialize(options)
-
super(options.reverse_merge(:case_sensitive => true))
-
end
-
-
# Unfortunately, we have to tie Uniqueness validators to a class.
-
1
def setup(klass)
-
@klass = klass
-
end
-
-
1
def validate_each(record, attribute, value)
-
finder_class = find_finder_class_for(record)
-
table = finder_class.arel_table
-
-
coder = record.class.serialized_attributes[attribute.to_s]
-
-
if value && coder
-
value = coder.dump value
-
end
-
-
relation = build_relation(finder_class, table, attribute, value)
-
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
-
-
Array(options[:scope]).each do |scope_item|
-
reflection = record.class.reflect_on_association(scope_item)
-
if reflection
-
scope_value = record.send(reflection.foreign_key)
-
scope_item = reflection.foreign_key
-
else
-
scope_value = record.read_attribute(scope_item)
-
end
-
relation = relation.and(table[scope_item].eq(scope_value))
-
end
-
-
relation = finder_class.unscoped.where(relation)
-
-
if options[:conditions]
-
relation = relation.merge(options[:conditions])
-
end
-
-
if relation.exists?
-
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope, :conditions).merge(:value => value))
-
end
-
end
-
-
1
protected
-
-
# The check for an existing value should be run from a class that
-
# isn't abstract. This means working down from the current class
-
# (self), to the first non-abstract class. Since classes don't know
-
# their subclasses, we have to build the hierarchy between self and
-
# the record's class.
-
1
def find_finder_class_for(record) #:nodoc:
-
class_hierarchy = [record.class]
-
-
while class_hierarchy.first != @klass
-
class_hierarchy.prepend(class_hierarchy.first.superclass)
-
end
-
-
class_hierarchy.detect { |klass| !klass.abstract_class? }
-
end
-
-
1
def build_relation(klass, table, attribute, value) #:nodoc:
-
if reflection = klass.reflect_on_association(attribute)
-
attribute = reflection.foreign_key
-
value = value.attributes[reflection.primary_key_column.name]
-
end
-
-
column = klass.columns_hash[attribute.to_s]
-
value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text?
-
-
if !options[:case_sensitive] && value && column.text?
-
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
-
relation = klass.connection.case_insensitive_comparison(table, attribute, column, value)
-
else
-
value = klass.connection.case_sensitive_modifier(value) unless value.nil?
-
relation = table[attribute].eq(value)
-
end
-
-
relation
-
end
-
end
-
-
1
module ClassMethods
-
# Validates whether the value of the specified attributes are unique
-
# across the system. Useful for making sure that only one user
-
# can be named "davidhh".
-
#
-
# class Person < ActiveRecord::Base
-
# validates_uniqueness_of :user_name
-
# end
-
#
-
# It can also validate whether the value of the specified attributes are
-
# unique based on a <tt>:scope</tt> parameter:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_uniqueness_of :user_name, scope: :account_id
-
# end
-
#
-
# Or even multiple scope parameters. For example, making sure that a
-
# teacher can only be on the schedule once per semester for a particular
-
# class.
-
#
-
# class TeacherSchedule < ActiveRecord::Base
-
# validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
-
# end
-
#
-
# It is also possible to limit the uniqueness constraint to a set of
-
# records matching certain conditions. In this example archived articles
-
# are not being taken into consideration when validating uniqueness
-
# of the title attribute:
-
#
-
# class Article < ActiveRecord::Base
-
# validates_uniqueness_of :title, conditions: where('status != ?', 'archived')
-
# end
-
#
-
# When the record is created, a check is performed to make sure that no
-
# record exists in the database with the given value for the specified
-
# attribute (that maps to a column). When the record is updated,
-
# the same check is made but disregarding the record itself.
-
#
-
# Configuration options:
-
#
-
# * <tt>:message</tt> - Specifies a custom error message (default is:
-
# "has already been taken").
-
# * <tt>:scope</tt> - One or more columns by which to limit the scope of
-
# the uniqueness constraint.
-
# * <tt>:conditions</tt> - Specify the conditions to be included as a
-
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
-
# (e.g. <tt>conditions: where('status = ?', 'active')</tt>).
-
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
-
# non-text columns (+true+ by default).
-
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
-
# attribute is blank (default is +false+).
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should ot occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
#
-
# === Concurrency and integrity
-
#
-
# Using this validation method in conjunction with ActiveRecord::Base#save
-
# does not guarantee the absence of duplicate record insertions, because
-
# uniqueness checks on the application level are inherently prone to race
-
# conditions. For example, suppose that two users try to post a Comment at
-
# the same time, and a Comment's title must be unique. At the database-level,
-
# the actions performed by these users could be interleaved in the following manner:
-
#
-
# User 1 | User 2
-
# ------------------------------------+--------------------------------------
-
# # User 1 checks whether there's |
-
# # already a comment with the title |
-
# # 'My Post'. This is not the case. |
-
# SELECT * FROM comments |
-
# WHERE title = 'My Post' |
-
# |
-
# | # User 2 does the same thing and also
-
# | # infers that his title is unique.
-
# | SELECT * FROM comments
-
# | WHERE title = 'My Post'
-
# |
-
# # User 1 inserts his comment. |
-
# INSERT INTO comments |
-
# (title, content) VALUES |
-
# ('My Post', 'hi!') |
-
# |
-
# | # User 2 does the same thing.
-
# | INSERT INTO comments
-
# | (title, content) VALUES
-
# | ('My Post', 'hello!')
-
# |
-
# | # ^^^^^^
-
# | # Boom! We now have a duplicate
-
# | # title!
-
#
-
# This could even happen if you use transactions with the 'serializable'
-
# isolation level. The best way to work around this problem is to add a unique
-
# index to the database table using
-
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
-
# rare case that a race condition occurs, the database will guarantee
-
# the field's uniqueness.
-
#
-
# When the database catches such a duplicate insertion,
-
# ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
-
# exception. You can either choose to let this error propagate (which
-
# will result in the default Rails exception page being shown), or you
-
# can catch it and restart the transaction (e.g. by telling the user
-
# that the title already exists, and asking him to re-enter the title).
-
# This technique is also known as optimistic concurrency control:
-
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control.
-
#
-
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
-
# constraint errors from other types of database errors by throwing an
-
# ActiveRecord::RecordNotUnique exception. For other adapters you will
-
# have to parse the (database-specific) exception message to detect such
-
# a case.
-
#
-
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
-
#
-
# * ActiveRecord::ConnectionAdapters::MysqlAdapter.
-
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
-
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
-
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
-
1
def validates_uniqueness_of(*attr_names)
-
validates_with UniquenessValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module VERSION #:nodoc:
-
1
MAJOR = 4
-
1
MINOR = 0
-
1
TINY = 0
-
1
PRE = "beta"
-
-
1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
-
end
-
end
-
#--
-
# Copyright (c) 2005-2012 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
1
require 'securerandom'
-
1
require "active_support/dependencies/autoload"
-
1
require "active_support/version"
-
1
require "active_support/logger"
-
1
require "active_support/lazy_load_hooks"
-
-
1
module ActiveSupport
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Concern
-
1
autoload :Dependencies
-
1
autoload :DescendantsTracker
-
1
autoload :FileUpdateChecker
-
1
autoload :LogSubscriber
-
1
autoload :Notifications
-
-
1
eager_autoload do
-
1
autoload :BacktraceCleaner
-
1
autoload :BasicObject
-
1
autoload :Benchmarkable
-
1
autoload :Cache
-
1
autoload :Callbacks
-
1
autoload :Configurable
-
1
autoload :Deprecation
-
1
autoload :Gzip
-
1
autoload :Inflector
-
1
autoload :JSON
-
1
autoload :KeyGenerator
-
1
autoload :MessageEncryptor
-
1
autoload :MessageVerifier
-
1
autoload :Multibyte
-
1
autoload :OptionMerger
-
1
autoload :OrderedHash
-
1
autoload :OrderedOptions
-
1
autoload :StringInquirer
-
1
autoload :TaggedLogging
-
1
autoload :XmlMini
-
end
-
-
1
autoload :Rescuable
-
1
autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
-
1
autoload :TestCase
-
end
-
-
1
autoload :I18n, "active_support/i18n"
-
1
module ActiveSupport
-
# Backtraces often include many lines that are not relevant for the context
-
# under review. This makes it hard to find the signal amongst the backtrace
-
# noise, and adds debugging time. With a BacktraceCleaner, filters and
-
# silencers are used to remove the noisy lines, so that only the most relevant
-
# lines remain.
-
#
-
# Filters are used to modify lines of data, while silencers are used to remove
-
# lines entirely. The typical filter use case is to remove lengthy path
-
# information from the start of each line, and view file paths relevant to the
-
# app directory instead of the file system root. The typical silencer use case
-
# is to exclude the output of a noisy library from the backtrace, so that you
-
# can focus on the rest.
-
#
-
# bc = BacktraceCleaner.new
-
# bc.add_filter { |line| line.gsub(Rails.root, '') }
-
# bc.add_silencer { |line| line =~ /mongrel|rubygems/ }
-
# bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems
-
#
-
# To reconfigure an existing BacktraceCleaner (like the default one in Rails)
-
# and show as much data as possible, you can always call
-
# <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
-
# backtrace to a pristine state. If you need to reconfigure an existing
-
# BacktraceCleaner so that it does not filter or modify the paths of any lines
-
# of the backtrace, you can call BacktraceCleaner#remove_filters! These two
-
# methods will give you a completely untouched backtrace.
-
#
-
# Inspired by the Quiet Backtrace gem by Thoughtbot.
-
1
class BacktraceCleaner
-
1
def initialize
-
1
@filters, @silencers = [], []
-
end
-
-
# Returns the backtrace after all filters and silencers have been run
-
# against it. Filters run first, then silencers.
-
1
def clean(backtrace, kind = :silent)
-
2
filtered = filter_backtrace(backtrace)
-
-
2
case kind
-
when :silent
-
1
silence(filtered)
-
when :noise
-
1
noise(filtered)
-
else
-
filtered
-
end
-
end
-
1
alias :filter :clean
-
-
# Adds a filter from the block provided. Each line in the backtrace will be
-
# mapped against this filter.
-
#
-
# # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
-
# backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
-
1
def add_filter(&block)
-
@filters << block
-
end
-
-
# Adds a silencer from the block provided. If the silencer returns +true+
-
# for a given line, it will be excluded from the clean backtrace.
-
#
-
# # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb"
-
# backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ }
-
1
def add_silencer(&block)
-
1
@silencers << block
-
end
-
-
# Will remove all silencers, but leave in the filters. This is useful if
-
# your context of debugging suddenly expands as you suspect a bug in one of
-
# the libraries you use.
-
1
def remove_silencers!
-
@silencers = []
-
end
-
-
1
def remove_filters!
-
@filters = []
-
end
-
-
1
private
-
1
def filter_backtrace(backtrace)
-
2
@filters.each do |f|
-
backtrace = backtrace.map { |line| f.call(line) }
-
end
-
-
2
backtrace
-
end
-
-
1
def silence(backtrace)
-
1
@silencers.each do |s|
-
25
backtrace = backtrace.reject { |line| s.call(line) }
-
end
-
-
1
backtrace
-
end
-
-
1
def noise(backtrace)
-
1
@silencers.each do |s|
-
25
backtrace = backtrace.select { |line| s.call(line) }
-
end
-
-
1
backtrace
-
end
-
end
-
end
-
1
module ActiveSupport
-
# A class with no predefined methods that behaves similarly to Builder's
-
# BlankSlate. Used for proxy classes.
-
1
class BasicObject < ::BasicObject
-
1
undef_method :==
-
1
undef_method :equal?
-
-
# Let ActiveSupport::BasicObject at least raise exceptions.
-
1
def raise(*args)
-
::Object.send(:raise, *args)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/benchmark'
-
1
require 'active_support/core_ext/hash/keys'
-
-
1
module ActiveSupport
-
1
module Benchmarkable
-
# Allows you to measure the execution time of a block in a template and
-
# records the result to the log. Wrap this block around expensive operations
-
# or possible bottlenecks to get a time reading for the operation. For
-
# example, let's say you thought your file processing method was taking too
-
# long; you could wrap it in a benchmark block.
-
#
-
# <% benchmark 'Process data files' do %>
-
# <%= expensive_files_operation %>
-
# <% end %>
-
#
-
# That would add something like "Process data files (345.2ms)" to the log,
-
# which you can then use to compare timings when optimizing your code.
-
#
-
# You may give an optional logger level (<tt>:debug</tt>, <tt>:info</tt>,
-
# <tt>:warn</tt>, <tt>:error</tt>) as the <tt>:level</tt> option. The
-
# default logger level value is <tt>:info</tt>.
-
#
-
# <% benchmark 'Low-level files', level: :debug do %>
-
# <%= lowlevel_files_operation %>
-
# <% end %>
-
#
-
# Finally, you can pass true as the third argument to silence all log
-
# activity (other than the timing information) from inside the block. This
-
# is great for boiling down a noisy block to just a single statement that
-
# produces one log line:
-
#
-
# <% benchmark 'Process data files', level: :info, silence: true do %>
-
# <%= expensive_and_chatty_files_operation %>
-
# <% end %>
-
1
def benchmark(message = "Benchmarking", options = {})
-
3
if logger
-
3
options.assert_valid_keys(:level, :silence)
-
3
options[:level] ||= :info
-
-
3
result = nil
-
6
ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield }
-
3
logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
-
3
result
-
else
-
yield
-
end
-
end
-
-
# Silence the logger during the execution of the block.
-
1
def silence
-
message = "ActiveSupport::Benchmarkable#silence is deprecated. It will be removed from Rails 4.1."
-
ActiveSupport::Deprecation.warn message
-
old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
-
yield
-
ensure
-
logger.level = old_logger_level if logger
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
1
require 'active_support/logger'
-
-
1
module ActiveSupport
-
1
BufferedLogger = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
-
'BufferedLogger', '::ActiveSupport::Logger')
-
end
-
1
require 'benchmark'
-
1
require 'zlib'
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/array/wrap'
-
1
require 'active_support/core_ext/benchmark'
-
1
require 'active_support/core_ext/exception'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/numeric/bytes'
-
1
require 'active_support/core_ext/numeric/time'
-
1
require 'active_support/core_ext/object/to_param'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
module ActiveSupport
-
# See ActiveSupport::Cache::Store for documentation.
-
1
module Cache
-
1
autoload :FileStore, 'active_support/cache/file_store'
-
1
autoload :MemoryStore, 'active_support/cache/memory_store'
-
1
autoload :MemCacheStore, 'active_support/cache/mem_cache_store'
-
1
autoload :NullStore, 'active_support/cache/null_store'
-
-
# These options mean something to all cache implementations. Individual cache
-
# implementations may support additional options.
-
1
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
-
-
1
module Strategy
-
1
autoload :LocalCache, 'active_support/cache/strategy/local_cache'
-
end
-
-
1
class << self
-
# Creates a new CacheStore object according to the given options.
-
#
-
# If no arguments are passed to this method, then a new
-
# ActiveSupport::Cache::MemoryStore object will be returned.
-
#
-
# If you pass a Symbol as the first argument, then a corresponding cache
-
# store class under the ActiveSupport::Cache namespace will be created.
-
# For example:
-
#
-
# ActiveSupport::Cache.lookup_store(:memory_store)
-
# # => returns a new ActiveSupport::Cache::MemoryStore object
-
#
-
# ActiveSupport::Cache.lookup_store(:mem_cache_store)
-
# # => returns a new ActiveSupport::Cache::MemCacheStore object
-
#
-
# Any additional arguments will be passed to the corresponding cache store
-
# class's constructor:
-
#
-
# ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache')
-
# # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache')
-
#
-
# If the first argument is not a Symbol, then it will simply be returned:
-
#
-
# ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
-
# # => returns MyOwnCacheStore.new
-
1
def lookup_store(*store_option)
-
34
store, *parameters = *Array.wrap(store_option).flatten
-
-
34
case store
-
when Symbol
-
17
store_class_name = store.to_s.camelize
-
17
store_class =
-
begin
-
17
require "active_support/cache/#{store}"
-
rescue LoadError => e
-
raise "Could not find cache store adapter for #{store} (#{e})"
-
else
-
17
ActiveSupport::Cache.const_get(store_class_name)
-
end
-
17
store_class.new(*parameters)
-
when nil
-
ActiveSupport::Cache::MemoryStore.new
-
else
-
17
store
-
end
-
end
-
-
1
def expand_cache_key(key, namespace = nil)
-
41
expanded_cache_key = namespace ? "#{namespace}/" : ""
-
-
41
if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
-
expanded_cache_key << "#{prefix}/"
-
end
-
-
41
expanded_cache_key << retrieve_cache_key(key)
-
41
expanded_cache_key
-
end
-
-
1
private
-
-
1
def retrieve_cache_key(key)
-
case
-
4
when key.respond_to?(:cache_key) then key.cache_key
-
94
when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
-
when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
-
63
else key.to_param
-
101
end.to_s
-
end
-
end
-
-
# An abstract cache store class. There are multiple cache store
-
# implementations, each having its own additional features. See the classes
-
# under the ActiveSupport::Cache module, e.g.
-
# ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most
-
# popular cache store for large production websites.
-
#
-
# Some implementations may not support all methods beyond the basic cache
-
# methods of +fetch+, +write+, +read+, +exist?+, and +delete+.
-
#
-
# ActiveSupport::Cache::Store can store any serializable Ruby object.
-
#
-
# cache = ActiveSupport::Cache::MemoryStore.new
-
#
-
# cache.read('city') # => nil
-
# cache.write('city', "Duckburgh")
-
# cache.read('city') # => "Duckburgh"
-
#
-
# Keys are always translated into Strings and are case sensitive. When an
-
# object is specified as a key and has a +cache_key+ method defined, this
-
# method will be called to define the key. Otherwise, the +to_param+
-
# method will be called. Hashes and Arrays can also be used as keys. The
-
# elements will be delimited by slashes, and the elements within a Hash
-
# will be sorted by key so they are consistent.
-
#
-
# cache.read('city') == cache.read(:city) # => true
-
#
-
# Nil values can be cached.
-
#
-
# If your cache is on a shared infrastructure, you can define a namespace
-
# for your cache entries. If a namespace is defined, it will be prefixed on
-
# to every key. The namespace can be either a static value or a Proc. If it
-
# is a Proc, it will be invoked when each key is evaluated so that you can
-
# use application logic to invalidate keys.
-
#
-
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
-
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
-
#
-
# Caches can also store values in a compressed format to save space and
-
# reduce time spent sending data. Since there is overhead, values must be
-
# large enough to warrant compression. To turn on compression either pass
-
# <tt>compress: true</tt> in the initializer or as an option to +fetch+
-
# or +write+. To specify the threshold at which to compress values, set the
-
# <tt>:compress_threshold</tt> option. The default threshold is 16K.
-
1
class Store
-
-
1
cattr_accessor :logger, :instance_writer => true
-
-
1
attr_reader :silence, :options
-
1
alias :silence? :silence
-
-
# Create a new cache. The options will be passed to any write method calls
-
# except for <tt>:namespace</tt> which can be used to set the global
-
# namespace for the cache.
-
1
def initialize(options = nil)
-
43
@options = options ? options.dup : {}
-
end
-
-
# Silence the logger.
-
1
def silence!
-
@silence = true
-
self
-
end
-
-
# Silence the logger within a block.
-
1
def mute
-
previous_silence, @silence = defined?(@silence) && @silence, true
-
yield
-
ensure
-
@silence = previous_silence
-
end
-
-
# Set to +true+ if cache stores should be instrumented.
-
# Default is +false+.
-
1
def self.instrument=(boolean)
-
Thread.current[:instrument_cache_store] = boolean
-
end
-
-
1
def self.instrument
-
79
Thread.current[:instrument_cache_store] || false
-
end
-
-
# Fetches data from the cache, using the given key. If there is data in
-
# the cache with the given key, then that data is returned.
-
#
-
# If there is no such data in the cache (a cache miss), then +nil+ will be
-
# returned. However, if a block has been passed, that block will be passed
-
# the key and executed in the event of a cache miss. The return value of the
-
# block will be written to the cache under the given cache key, and that
-
# return value will be returned.
-
#
-
# cache.write('today', 'Monday')
-
# cache.fetch('today') # => "Monday"
-
#
-
# cache.fetch('city') # => nil
-
# cache.fetch('city') do
-
# 'Duckburgh'
-
# end
-
# cache.fetch('city') # => "Duckburgh"
-
#
-
# You may also specify additional options via the +options+ argument.
-
# Setting <tt>force: true</tt> will force a cache miss:
-
#
-
# cache.write('today', 'Monday')
-
# cache.fetch('today', force: true) # => nil
-
#
-
# Setting <tt>:compress</tt> will store a large cache entry set by the call
-
# in a compressed format.
-
#
-
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
-
# All caches support auto-expiring content after a specified number of
-
# seconds. This value can be specified as an option to the constructor
-
# (in which case all entries will be affected), or it can be supplied to
-
# the +fetch+ or +write+ method to effect just one entry.
-
#
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
-
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
-
#
-
# Setting <tt>:race_condition_ttl</tt> is very useful in situations where
-
# a cache entry is used very frequently and is under heavy load. If a
-
# cache expires and due to heavy load seven different processes will try
-
# to read data natively and then they all will try to write to cache. To
-
# avoid that case the first process to find an expired cache entry will
-
# bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
-
# Yes, this process is extending the time for a stale value by another few
-
# seconds. Because of extended life of the previous cache, other processes
-
# will continue to use slightly stale data for a just a big longer. In the
-
# meantime that first process will go ahead and will write into cache the
-
# new value. After that all the processes will start getting new value.
-
# The key is to keep <tt>:race_condition_ttl</tt> small.
-
#
-
# If the process regenerating the entry errors out, the entry will be
-
# regenerated after the specified number of seconds. Also note that the
-
# life of stale cache is extended only if it expired recently. Otherwise
-
# a new value is generated and <tt>:race_condition_ttl</tt> does not play
-
# any role.
-
#
-
# # Set all values to expire after one minute.
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
-
#
-
# cache.write('foo', 'original value')
-
# val_1 = nil
-
# val_2 = nil
-
# sleep 60
-
#
-
# Thread.new do
-
# val_1 = cache.fetch('foo', race_condition_ttl: 10) do
-
# sleep 1
-
# 'new value 1'
-
# end
-
# end
-
#
-
# Thread.new do
-
# val_2 = cache.fetch('foo', race_condition_ttl: 10) do
-
# 'new value 2'
-
# end
-
# end
-
#
-
# # val_1 => "new value 1"
-
# # val_2 => "original value"
-
# # sleep 10 # First thread extend the life of cache by another 10 seconds
-
# # cache.fetch('foo') => "new value 1"
-
#
-
# Other options will be handled by the specific cache store implementation.
-
# Internally, #fetch calls #read_entry, and calls #write_entry on a cache
-
# miss. +options+ will be passed to the #read and #write calls.
-
#
-
# For example, MemCacheStore's #write method supports the +:raw+
-
# option, which tells the memcached server to store all values as strings.
-
# We can use this option with #fetch too:
-
#
-
# cache = ActiveSupport::Cache::MemCacheStore.new
-
# cache.fetch("foo", force: true, raw: true) do
-
# :bar
-
# end
-
# cache.fetch('foo') # => "bar"
-
1
def fetch(name, options = nil)
-
if block_given?
-
options = merged_options(options)
-
key = namespaced_key(name, options)
-
unless options[:force]
-
entry = instrument(:read, name, options) do |payload|
-
payload[:super_operation] = :fetch if payload
-
read_entry(key, options)
-
end
-
end
-
if entry && entry.expired?
-
race_ttl = options[:race_condition_ttl].to_i
-
if race_ttl && (Time.now - entry.expires_at <= race_ttl)
-
# When an entry has :race_condition_ttl defined, put the stale entry back into the cache
-
# for a brief period while the entry is begin recalculated.
-
entry.expires_at = Time.now + race_ttl
-
write_entry(key, entry, :expires_in => race_ttl * 2)
-
else
-
delete_entry(key, options)
-
end
-
entry = nil
-
end
-
-
if entry
-
instrument(:fetch_hit, name, options) { |payload| }
-
entry.value
-
else
-
result = instrument(:generate, name, options) do |payload|
-
yield(name)
-
end
-
write(name, result, options)
-
result
-
end
-
else
-
read(name, options)
-
end
-
end
-
-
# Fetches data from the cache, using the given key. If there is data in
-
# the cache with the given key, then that data is returned. Otherwise,
-
# +nil+ is returned.
-
#
-
# Options are passed to the underlying cache implementation.
-
1
def read(name, options = nil)
-
41
options = merged_options(options)
-
41
key = namespaced_key(name, options)
-
41
instrument(:read, name, options) do |payload|
-
41
entry = read_entry(key, options)
-
41
if entry
-
17
if entry.expired?
-
delete_entry(key, options)
-
payload[:hit] = false if payload
-
nil
-
else
-
17
payload[:hit] = true if payload
-
17
entry.value
-
end
-
else
-
24
payload[:hit] = false if payload
-
24
nil
-
end
-
end
-
end
-
-
# Read multiple values at once from the cache. Options can be passed
-
# in the last argument.
-
#
-
# Some cache implementation may optimize this method.
-
#
-
# Returns a hash mapping the names provided to the values found.
-
1
def read_multi(*names)
-
options = names.extract_options!
-
options = merged_options(options)
-
results = {}
-
names.each do |name|
-
key = namespaced_key(name, options)
-
entry = read_entry(key, options)
-
if entry
-
if entry.expired?
-
delete_entry(key, options)
-
else
-
results[name] = entry.value
-
end
-
end
-
end
-
results
-
end
-
-
# Writes the value to the cache, with the key.
-
#
-
# Options are passed to the underlying cache implementation.
-
1
def write(name, value, options = nil)
-
32
options = merged_options(options)
-
32
instrument(:write, name, options) do |payload|
-
32
entry = Entry.new(value, options)
-
32
write_entry(namespaced_key(name, options), entry, options)
-
end
-
end
-
-
# Deletes an entry in the cache. Returns +true+ if an entry is deleted.
-
#
-
# Options are passed to the underlying cache implementation.
-
1
def delete(name, options = nil)
-
3
options = merged_options(options)
-
3
instrument(:delete, name) do |payload|
-
3
delete_entry(namespaced_key(name, options), options)
-
end
-
end
-
-
# Return +true+ if the cache contains an entry for the given key.
-
#
-
# Options are passed to the underlying cache implementation.
-
1
def exist?(name, options = nil)
-
2
options = merged_options(options)
-
2
instrument(:exist?, name) do |payload|
-
2
entry = read_entry(namespaced_key(name, options), options)
-
2
entry && !entry.expired?
-
end
-
end
-
-
# Delete all entries with keys matching the pattern.
-
#
-
# Options are passed to the underlying cache implementation.
-
#
-
# All implementations may not support this method.
-
1
def delete_matched(matcher, options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
-
end
-
-
# Increment an integer value in the cache.
-
#
-
# Options are passed to the underlying cache implementation.
-
#
-
# All implementations may not support this method.
-
1
def increment(name, amount = 1, options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support increment")
-
end
-
-
# Decrement an integer value in the cache.
-
#
-
# Options are passed to the underlying cache implementation.
-
#
-
# All implementations may not support this method.
-
1
def decrement(name, amount = 1, options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support decrement")
-
end
-
-
# Cleanup the cache by removing expired entries.
-
#
-
# Options are passed to the underlying cache implementation.
-
#
-
# All implementations may not support this method.
-
1
def cleanup(options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support cleanup")
-
end
-
-
# Clear the entire cache. Be careful with this method since it could
-
# affect other processes if shared cache is being used.
-
#
-
# Options are passed to the underlying cache implementation.
-
#
-
# All implementations may not support this method.
-
1
def clear(options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support clear")
-
end
-
-
1
protected
-
# Add the namespace defined in the options to a pattern designed to
-
# match keys. Implementations that support delete_matched should call
-
# this method to translate a pattern that matches names into one that
-
# matches namespaced keys.
-
1
def key_matcher(pattern, options)
-
1
prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
-
1
if prefix
-
source = pattern.source
-
if source.start_with?('^')
-
source = source[1, source.length]
-
else
-
source = ".*#{source[0, source.length]}"
-
end
-
Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options)
-
else
-
1
pattern
-
end
-
end
-
-
# Read an entry from the cache implementation. Subclasses must implement
-
# this method.
-
1
def read_entry(key, options) # :nodoc:
-
raise NotImplementedError.new
-
end
-
-
# Write an entry to the cache implementation. Subclasses must implement
-
# this method.
-
1
def write_entry(key, entry, options) # :nodoc:
-
raise NotImplementedError.new
-
end
-
-
# Delete an entry from the cache implementation. Subclasses must
-
# implement this method.
-
1
def delete_entry(key, options) # :nodoc:
-
raise NotImplementedError.new
-
end
-
-
1
private
-
# Merge the default options with ones specific to a method call.
-
1
def merged_options(call_options) # :nodoc:
-
79
if call_options
-
14
options.merge(call_options)
-
else
-
65
options.dup
-
end
-
end
-
-
# Expand key to be a consistent string value. Invoke +cache_key+ if
-
# object responds to +cache_key+. Otherwise, +to_param+ method will be
-
# called. If the key is a Hash, then keys will be sorted alphabetically.
-
1
def expanded_key(key) # :nodoc:
-
78
return key.cache_key.to_s if key.respond_to?(:cache_key)
-
-
78
case key
-
when Array
-
if key.size > 1
-
key = key.collect{|element| expanded_key(element)}
-
else
-
key = key.first
-
end
-
when Hash
-
key = key.sort_by { |k,_| k.to_s }.collect{|k,v| "#{k}=#{v}"}
-
end
-
-
78
key.to_param
-
end
-
-
# Prefix a key with the namespace. Namespace and key will be delimited
-
# with a colon.
-
1
def namespaced_key(key, options)
-
78
key = expanded_key(key)
-
78
namespace = options[:namespace] if options
-
78
prefix = namespace.is_a?(Proc) ? namespace.call : namespace
-
78
key = "#{prefix}:#{key}" if prefix
-
78
key
-
end
-
-
1
def instrument(operation, key, options = nil)
-
79
log(operation, key, options)
-
-
79
if self.class.instrument
-
payload = { :key => key }
-
payload.merge!(options) if options.is_a?(Hash)
-
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
-
else
-
79
yield(nil)
-
end
-
end
-
-
1
def log(operation, key, options = nil)
-
79
return unless logger && logger.debug? && !silence?
-
logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
-
end
-
end
-
-
# This class is used to represent cache entries. Cache entries have a value and an optional
-
# expiration time. The expiration time is used to support the :race_condition_ttl option
-
# on the cache.
-
#
-
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
-
# using short instance variable names that are lazily defined.
-
1
class Entry # :nodoc:
-
1
DEFAULT_COMPRESS_LIMIT = 16.kilobytes
-
-
# Create a new cache entry for the specified value. Options supported are
-
# +:compress+, +:compress_threshold+, and +:expires_in+.
-
1
def initialize(value, options = {})
-
32
if should_compress?(value, options)
-
@v = compress(value)
-
@c = true
-
else
-
32
@v = value
-
end
-
32
if expires_in = options[:expires_in]
-
@x = (Time.now + expires_in).to_i
-
end
-
end
-
-
1
def value
-
50
convert_version_3_entry! if defined?(@value)
-
50
compressed? ? uncompress(@v) : @v
-
end
-
-
# Check if the entry is expired. The +expires_in+ parameter can override
-
# the value set when the entry was created.
-
1
def expired?
-
18
convert_version_3_entry! if defined?(@value)
-
18
if defined?(@x)
-
@x && @x < Time.now.to_i
-
else
-
18
false
-
end
-
end
-
-
1
def expires_at
-
Time.at(@x) if defined?(@x)
-
end
-
-
1
def expires_at=(value)
-
@x = value.to_i
-
end
-
-
# Returns the size of the cached value. This could be less than
-
# <tt>value.size</tt> if the data is compressed.
-
1
def size
-
39
if defined?(@s)
-
6
@s
-
else
-
33
case value
-
when NilClass
-
0
-
when String
-
19
@v.bytesize
-
else
-
14
@s = Marshal.dump(@v).bytesize
-
end
-
end
-
end
-
-
# Duplicate the value in a class. This is used by cache implementations that don't natively
-
# serialize entries to protect against accidental cache modifications.
-
1
def dup_value!
-
30
convert_version_3_entry! if defined?(@value)
-
30
if @v && !compressed? && !(@v.is_a?(Numeric) || @v == true || @v == false)
-
30
if @v.is_a?(String)
-
16
@v = @v.dup
-
else
-
14
@v = Marshal.load(Marshal.dump(@v))
-
end
-
end
-
end
-
-
1
private
-
1
def should_compress?(value, options)
-
32
if value && options[:compress]
-
compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
-
serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
-
return true if serialized_value_size >= compress_threshold
-
end
-
32
false
-
end
-
-
1
def compressed?
-
80
defined?(@c) ? @c : false
-
end
-
-
1
def compress(value)
-
Zlib::Deflate.deflate(Marshal.dump(value))
-
end
-
-
1
def uncompress(value)
-
Marshal.load(Zlib::Inflate.inflate(value))
-
end
-
-
# The internals of this method changed between Rails 3.x and 4.0. This method provides the glue
-
# to ensure that cache entries created under the old version still work with the new class definition.
-
1
def convert_version_3_entry!
-
if defined?(@value)
-
@v = @value
-
remove_instance_variable(:@value)
-
end
-
if defined?(@compressed)
-
@c = @compressed
-
remove_instance_variable(:@compressed)
-
end
-
if defined?(@expires_in) && defined?(@created_at) && @expires_in && @created_at
-
@x = (@created_at + @expires_in).to_i
-
remove_instance_variable(:@created_at)
-
remove_instance_variable(:@expires_in)
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/file/atomic'
-
1
require 'active_support/core_ext/string/conversions'
-
1
require 'uri/common'
-
-
1
module ActiveSupport
-
1
module Cache
-
# A cache store implementation which stores everything on the filesystem.
-
#
-
# FileStore implements the Strategy::LocalCache strategy which implements
-
# an in-memory cache inside of a block.
-
1
class FileStore < Store
-
1
attr_reader :cache_path
-
-
1
DIR_FORMATTER = "%03X"
-
1
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
-
1
EXCLUDED_DIRS = ['.', '..'].freeze
-
-
1
def initialize(cache_path, options = nil)
-
17
super(options)
-
17
@cache_path = cache_path.to_s
-
17
extend Strategy::LocalCache
-
end
-
-
1
def clear(options = nil)
-
root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
-
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
-
end
-
-
1
def cleanup(options = nil)
-
options = merged_options(options)
-
each_key(options) do |key|
-
entry = read_entry(key, options)
-
delete_entry(key, options) if entry && entry.expired?
-
end
-
end
-
-
1
def increment(name, amount = 1, options = nil)
-
file_name = key_file_path(namespaced_key(name, options))
-
lock_file(file_name) do
-
options = merged_options(options)
-
if num = read(name, options)
-
num = num.to_i + amount
-
write(name, num, options)
-
num
-
else
-
nil
-
end
-
end
-
end
-
-
1
def decrement(name, amount = 1, options = nil)
-
file_name = key_file_path(namespaced_key(name, options))
-
lock_file(file_name) do
-
options = merged_options(options)
-
if num = read(name, options)
-
num = num.to_i - amount
-
write(name, num, options)
-
num
-
else
-
nil
-
end
-
end
-
end
-
-
1
def delete_matched(matcher, options = nil)
-
options = merged_options(options)
-
instrument(:delete_matched, matcher.inspect) do
-
matcher = key_matcher(matcher, options)
-
search_dir(cache_path) do |path|
-
key = file_path_key(path)
-
delete_entry(key, options) if key.match(matcher)
-
end
-
end
-
end
-
-
1
protected
-
-
1
def read_entry(key, options)
-
2
file_name = key_file_path(key)
-
2
if File.exist?(file_name)
-
File.open(file_name) { |f| Marshal.load(f) }
-
end
-
rescue => e
-
logger.error("FileStoreError (#{e}): #{e.message}") if logger
-
nil
-
end
-
-
1
def write_entry(key, entry, options)
-
2
file_name = key_file_path(key)
-
2
ensure_cache_path(File.dirname(file_name))
-
4
File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)}
-
2
true
-
end
-
-
1
def delete_entry(key, options)
-
file_name = key_file_path(key)
-
if File.exist?(file_name)
-
begin
-
File.delete(file_name)
-
delete_empty_directories(File.dirname(file_name))
-
true
-
rescue => e
-
# Just in case the error was caused by another process deleting the file first.
-
raise e if File.exist?(file_name)
-
false
-
end
-
end
-
end
-
-
1
private
-
# Lock a file for a block so only one process can modify it at a time.
-
1
def lock_file(file_name, &block) # :nodoc:
-
if File.exist?(file_name)
-
File.open(file_name, 'r+') do |f|
-
begin
-
f.flock File::LOCK_EX
-
yield
-
ensure
-
f.flock File::LOCK_UN
-
end
-
end
-
else
-
yield
-
end
-
end
-
-
# Translate a key into a file path.
-
1
def key_file_path(key)
-
4
fname = URI.encode_www_form_component(key)
-
4
hash = Zlib.adler32(fname)
-
4
hash, dir_1 = hash.divmod(0x1000)
-
4
dir_2 = hash.modulo(0x1000)
-
4
fname_paths = []
-
-
# Make sure file name doesn't exceed file system limits.
-
begin
-
4
fname_paths << fname[0, FILENAME_MAX_SIZE]
-
4
fname = fname[FILENAME_MAX_SIZE..-1]
-
4
end until fname.blank?
-
-
4
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
-
end
-
-
# Translate a file path into a key.
-
1
def file_path_key(path)
-
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
-
URI.decode_www_form_component(fname, Encoding::UTF_8)
-
end
-
-
# Delete empty directories in the cache.
-
1
def delete_empty_directories(dir)
-
return if dir == cache_path
-
if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty?
-
File.delete(dir) rescue nil
-
delete_empty_directories(File.dirname(dir))
-
end
-
end
-
-
# Make sure a file path's directories exist.
-
1
def ensure_cache_path(path)
-
2
FileUtils.makedirs(path) unless File.exist?(path)
-
end
-
-
1
def search_dir(dir, &callback)
-
return if !File.exist?(dir)
-
Dir.foreach(dir) do |d|
-
next if EXCLUDED_DIRS.include?(d)
-
name = File.join(dir, d)
-
if File.directory?(name)
-
search_dir(name, &callback)
-
else
-
callback.call name
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'monitor'
-
-
1
module ActiveSupport
-
1
module Cache
-
# A cache store implementation which stores everything into memory in the
-
# same process. If you're running multiple Ruby on Rails server processes
-
# (which is the case if you're using mongrel_cluster or Phusion Passenger),
-
# then this means that Rails server process instances won't be able
-
# to share cache data with each other and this may not be the most
-
# appropriate cache in that scenario.
-
#
-
# This cache has a bounded size specified by the :size options to the
-
# initializer (default is 32Mb). When the cache exceeds the allotted size,
-
# a cleanup will occur which tries to prune the cache down to three quarters
-
# of the maximum size by removing the least recently used entries.
-
#
-
# MemoryStore is thread-safe.
-
1
class MemoryStore < Store
-
1
def initialize(options = nil)
-
26
options ||= {}
-
26
super(options)
-
26
@data = {}
-
26
@key_access = {}
-
26
@max_size = options[:size] || 32.megabytes
-
26
@max_prune_time = options[:max_prune_time] || 2
-
26
@cache_size = 0
-
26
@monitor = Monitor.new
-
26
@pruning = false
-
end
-
-
1
def clear(options = nil)
-
synchronize do
-
@data.clear
-
@key_access.clear
-
@cache_size = 0
-
end
-
end
-
-
1
def cleanup(options = nil)
-
options = merged_options(options)
-
instrument(:cleanup, :size => @data.size) do
-
keys = synchronize{ @data.keys }
-
keys.each do |key|
-
entry = @data[key]
-
delete_entry(key, options) if entry && entry.expired?
-
end
-
end
-
end
-
-
# To ensure entries fit within the specified memory prune the cache by removing the least
-
# recently accessed entries.
-
1
def prune(target_size, max_time = nil)
-
return if pruning?
-
@pruning = true
-
begin
-
start_time = Time.now
-
cleanup
-
instrument(:prune, target_size, :from => @cache_size) do
-
keys = synchronize{ @key_access.keys.sort{|a,b| @key_access[a].to_f <=> @key_access[b].to_f} }
-
keys.each do |key|
-
delete_entry(key, options)
-
return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
-
end
-
end
-
ensure
-
@pruning = false
-
end
-
end
-
-
# Returns true if the cache is currently being pruned.
-
1
def pruning?
-
@pruning
-
end
-
-
# Increment an integer value in the cache.
-
1
def increment(name, amount = 1, options = nil)
-
synchronize do
-
options = merged_options(options)
-
if num = read(name, options)
-
num = num.to_i + amount
-
write(name, num, options)
-
num
-
else
-
nil
-
end
-
end
-
end
-
-
# Decrement an integer value in the cache.
-
1
def decrement(name, amount = 1, options = nil)
-
synchronize do
-
options = merged_options(options)
-
if num = read(name, options)
-
num = num.to_i - amount
-
write(name, num, options)
-
num
-
else
-
nil
-
end
-
end
-
end
-
-
1
def delete_matched(matcher, options = nil)
-
1
options = merged_options(options)
-
1
instrument(:delete_matched, matcher.inspect) do
-
1
matcher = key_matcher(matcher, options)
-
2
keys = synchronize { @data.keys }
-
1
keys.each do |key|
-
3
delete_entry(key, options) if key.match(matcher)
-
end
-
end
-
end
-
-
1
def inspect # :nodoc:
-
"<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
-
end
-
-
# Synchronize calls to the cache. This should be called wherever the underlying cache implementation
-
# is not thread safe.
-
1
def synchronize(&block) # :nodoc:
-
77
@monitor.synchronize(&block)
-
end
-
-
1
protected
-
1
def read_entry(key, options) # :nodoc:
-
41
entry = @data[key]
-
41
synchronize do
-
41
if entry
-
18
@key_access[key] = Time.now.to_f
-
else
-
23
@key_access.delete(key)
-
end
-
end
-
41
entry
-
end
-
-
1
def write_entry(key, entry, options) # :nodoc:
-
30
entry.dup_value!
-
30
synchronize do
-
30
old_entry = @data[key]
-
30
return false if @data.key?(key) && options[:unless_exist]
-
30
@cache_size -= old_entry.size if old_entry
-
30
@cache_size += entry.size
-
30
@key_access[key] = Time.now.to_f
-
30
@data[key] = entry
-
30
prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
-
30
true
-
end
-
end
-
-
1
def delete_entry(key, options) # :nodoc:
-
5
synchronize do
-
5
@key_access.delete(key)
-
5
entry = @data.delete(key)
-
5
@cache_size -= entry.size if entry
-
5
!!entry
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
module ActiveSupport
-
1
module Cache
-
1
module Strategy
-
# Caches that implement LocalCache will be backed by an in-memory cache for the
-
# duration of a block. Repeated calls to the cache for the same key will hit the
-
# in-memory cache for faster access.
-
1
module LocalCache
-
# Simple memory backed cache. This cache is not thread safe and is intended only
-
# for serving as a temporary memory cache for a single thread.
-
1
class LocalStore < Store
-
1
def initialize
-
super
-
@data = {}
-
end
-
-
# Don't allow synchronizing since it isn't thread safe,
-
1
def synchronize # :nodoc:
-
yield
-
end
-
-
1
def clear(options = nil)
-
@data.clear
-
end
-
-
1
def read_entry(key, options)
-
@data[key]
-
end
-
-
1
def write_entry(key, value, options)
-
@data[key] = value
-
true
-
end
-
-
1
def delete_entry(key, options)
-
!!@data.delete(key)
-
end
-
end
-
-
# Use a local cache for the duration of block.
-
1
def with_local_cache
-
save_val = Thread.current[thread_local_key]
-
begin
-
Thread.current[thread_local_key] = LocalStore.new
-
yield
-
ensure
-
Thread.current[thread_local_key] = save_val
-
end
-
end
-
-
#--
-
# This class wraps up local storage for middlewares. Only the middleware method should
-
# construct them.
-
1
class Middleware # :nodoc:
-
1
attr_reader :name, :thread_local_key
-
-
1
def initialize(name, thread_local_key)
-
@name = name
-
@thread_local_key = thread_local_key
-
@app = nil
-
end
-
-
1
def new(app)
-
@app = app
-
self
-
end
-
-
1
def call(env)
-
Thread.current[thread_local_key] = LocalStore.new
-
@app.call(env)
-
ensure
-
Thread.current[thread_local_key] = nil
-
end
-
end
-
-
# Middleware class can be inserted as a Rack handler to be local cache for the
-
# duration of request.
-
1
def middleware
-
@middleware ||= Middleware.new(
-
"ActiveSupport::Cache::Strategy::LocalCache",
-
thread_local_key)
-
end
-
-
1
def clear(options = nil) # :nodoc:
-
local_cache.clear(options) if local_cache
-
super
-
end
-
-
1
def cleanup(options = nil) # :nodoc:
-
local_cache.clear(options) if local_cache
-
super
-
end
-
-
1
def increment(name, amount = 1, options = nil) # :nodoc:
-
value = bypass_local_cache{super}
-
if local_cache
-
local_cache.mute do
-
if value
-
local_cache.write(name, value, options)
-
else
-
local_cache.delete(name, options)
-
end
-
end
-
end
-
value
-
end
-
-
1
def decrement(name, amount = 1, options = nil) # :nodoc:
-
value = bypass_local_cache{super}
-
if local_cache
-
local_cache.mute do
-
if value
-
local_cache.write(name, value, options)
-
else
-
local_cache.delete(name, options)
-
end
-
end
-
end
-
value
-
end
-
-
1
protected
-
1
def read_entry(key, options) # :nodoc:
-
2
if local_cache
-
entry = local_cache.read_entry(key, options)
-
unless entry
-
entry = super
-
local_cache.write_entry(key, entry, options)
-
end
-
entry
-
else
-
2
super
-
end
-
end
-
-
1
def write_entry(key, entry, options) # :nodoc:
-
2
local_cache.write_entry(key, entry, options) if local_cache
-
2
super
-
end
-
-
1
def delete_entry(key, options) # :nodoc:
-
local_cache.delete_entry(key, options) if local_cache
-
super
-
end
-
-
1
private
-
1
def thread_local_key
-
4
@thread_local_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym
-
end
-
-
1
def local_cache
-
4
Thread.current[thread_local_key]
-
end
-
-
1
def bypass_local_cache
-
save_cache = Thread.current[thread_local_key]
-
begin
-
Thread.current[thread_local_key] = nil
-
yield
-
ensure
-
Thread.current[thread_local_key] = save_cache
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/concern'
-
1
require 'active_support/descendants_tracker'
-
1
require 'active_support/core_ext/class/attribute'
-
1
require 'active_support/core_ext/kernel/reporting'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
-
1
module ActiveSupport
-
# Callbacks are code hooks that are run at key points in an object's lifecycle.
-
# The typical use case is to have a base class define a set of callbacks
-
# relevant to the other functionality it supplies, so that subclasses can
-
# install callbacks that enhance or modify the base functionality without
-
# needing to override or redefine methods of the base class.
-
#
-
# Mixing in this module allows you to define the events in the object's
-
# lifecycle that will support callbacks (via +ClassMethods.define_callbacks+),
-
# set the instance methods, procs, or callback objects to be called (via
-
# +ClassMethods.set_callback+), and run the installed callbacks at the
-
# appropriate times (via +run_callbacks+).
-
#
-
# Three kinds of callbacks are supported: before callbacks, run before a
-
# certain event; after callbacks, run after the event; and around callbacks,
-
# blocks that surround the event, triggering it when they yield. Callback code
-
# can be contained in instance methods, procs or lambdas, or callback objects
-
# that respond to certain predetermined methods. See +ClassMethods.set_callback+
-
# for details.
-
#
-
# class Record
-
# include ActiveSupport::Callbacks
-
# define_callbacks :save
-
#
-
# def save
-
# run_callbacks :save do
-
# puts "- save"
-
# end
-
# end
-
# end
-
#
-
# class PersonRecord < Record
-
# set_callback :save, :before, :saving_message
-
# def saving_message
-
# puts "saving..."
-
# end
-
#
-
# set_callback :save, :after do |object|
-
# puts "saved"
-
# end
-
# end
-
#
-
# person = PersonRecord.new
-
# person.save
-
#
-
# Output:
-
# saving...
-
# - save
-
# saved
-
1
module Callbacks
-
1
extend Concern
-
-
1
included do
-
10
extend ActiveSupport::DescendantsTracker
-
end
-
-
# Runs the callbacks for the given event.
-
#
-
# Calls the before and around callbacks in the order they were set, yields
-
# the block (if given one), and then runs the after callbacks in reverse
-
# order.
-
#
-
# If the callback chain was halted, returns +false+. Otherwise returns the
-
# result of the block, or +true+ if no block is given.
-
#
-
# run_callbacks :save do
-
# save
-
# end
-
1
def run_callbacks(kind, &block)
-
9754
runner_name = self.class.__define_callbacks(kind, self)
-
9754
send(runner_name, &block)
-
end
-
-
1
private
-
-
# A hook invoked everytime a before callback is halted.
-
# This can be overridden in AS::Callback implementors in order
-
# to provide better debugging/logging.
-
1
def halted_callback_hook(filter)
-
end
-
-
1
class Callback #:nodoc:#
-
1
@@_callback_sequence = 0
-
-
1
attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
-
-
1
def initialize(chain, filter, kind, options, klass)
-
180
@chain, @kind, @klass = chain, kind, klass
-
180
deprecate_per_key_option(options)
-
180
normalize_options!(options)
-
-
180
@raw_filter, @options = filter, options
-
180
@filter = _compile_filter(filter)
-
180
recompile_options!
-
end
-
-
1
def deprecate_per_key_option(options)
-
185
if options[:per_key]
-
raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead."
-
end
-
end
-
-
1
def clone(chain, klass)
-
5
obj = super()
-
5
obj.chain = chain
-
5
obj.klass = klass
-
5
obj.options = @options.dup
-
5
obj.options[:if] = @options[:if].dup
-
5
obj.options[:unless] = @options[:unless].dup
-
5
obj
-
end
-
-
1
def normalize_options!(options)
-
180
options[:if] = Array(options[:if])
-
180
options[:unless] = Array(options[:unless])
-
end
-
-
1
def name
-
2
chain.name
-
end
-
-
1
def next_id
-
365
@@_callback_sequence += 1
-
end
-
-
1
def matches?(_kind, _filter)
-
224
@kind == _kind && @filter == _filter
-
end
-
-
1
def _update_filter(filter_options, new_options)
-
5
filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
-
5
filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
-
end
-
-
1
def recompile!(_options)
-
5
deprecate_per_key_option(_options)
-
5
_update_filter(self.options, _options)
-
-
5
recompile_options!
-
end
-
-
# Wraps code with filter
-
1
def apply(code)
-
828
case @kind
-
when :before
-
<<-RUBY_EVAL
-
540
if !halted && #{@compiled_options}
-
# This double assignment is to prevent warnings in 1.9.3 as
-
# the `result` variable is not always used except if the
-
# terminator code refers to it.
-
result = result = #{@filter}
-
halted = (#{chain.config[:terminator]})
-
if halted
-
halted_callback_hook(#{@raw_filter.inspect.inspect})
-
end
-
end
-
#{code}
-
RUBY_EVAL
-
when :after
-
<<-RUBY_EVAL
-
265
#{code}
-
if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options}
-
#{@filter}
-
end
-
RUBY_EVAL
-
when :around
-
23
name = define_conditional_callback
-
<<-RUBY_EVAL
-
23
#{name}(halted) do
-
#{code}
-
value
-
end
-
RUBY_EVAL
-
end
-
end
-
-
1
private
-
-
# Compile around filters with conditions into proxy methods
-
# that contain the conditions.
-
#
-
# For `set_callback :save, :around, :filter_name, if: :condition':
-
#
-
# def _conditional_callback_save_17
-
# if condition
-
# filter_name do
-
# yield self
-
# end
-
# else
-
# yield self
-
# end
-
# end
-
1
def define_conditional_callback
-
23
name = "_conditional_callback_#{@kind}_#{next_id}"
-
23
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
-
def #{name}(halted)
-
if #{@compiled_options} && !halted
-
#{@filter} do
-
yield self
-
end
-
else
-
yield self
-
end
-
end
-
RUBY_EVAL
-
23
name
-
end
-
-
# Options support the same options as filters themselves (and support
-
# symbols, string, procs, and objects), so compile a conditional
-
# expression based on the options.
-
1
def recompile_options!
-
185
conditions = ["true"]
-
-
185
unless options[:if].empty?
-
61
conditions << Array(_compile_filter(options[:if]))
-
end
-
-
185
unless options[:unless].empty?
-
40
conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
-
end
-
-
185
@compiled_options = conditions.flatten.join(" && ")
-
end
-
-
# Filters support:
-
#
-
# Arrays:: Used in conditions. This is used to specify
-
# multiple conditions. Used internally to
-
# merge conditions from skip_* filters.
-
# Symbols:: A method to call.
-
# Strings:: Some content to evaluate.
-
# Procs:: A proc to call with the object.
-
# Objects:: An object with a <tt>before_foo</tt> method on it to call.
-
#
-
# All of these objects are compiled into methods and handled
-
# the same after this point:
-
#
-
# Arrays:: Merged together into a single filter.
-
# Symbols:: Already methods.
-
# Strings:: class_eval'ed into methods.
-
# Procs:: define_method'ed into methods.
-
# Objects::
-
# a method is created that calls the before_foo method
-
# on the object.
-
1
def _compile_filter(filter)
-
342
method_name = "_callback_#{@kind}_#{next_id}"
-
342
case filter
-
when Array
-
162
filter.map {|f| _compile_filter(f)}
-
when Symbol
-
118
filter
-
when String
-
79
"(#{filter})"
-
when Proc
-
48
@klass.send(:define_method, method_name, &filter)
-
48
return method_name if filter.arity <= 0
-
-
13
method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
-
else
-
31
@klass.send(:define_method, "#{method_name}_object") { filter }
-
-
16
_normalize_legacy_filter(kind, filter)
-
16
scopes = Array(chain.config[:scope])
-
32
method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
-
-
16
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
-
def #{method_name}(&blk)
-
#{method_name}_object.send(:#{method_to_call}, self, &blk)
-
end
-
RUBY_EVAL
-
-
16
method_name
-
end
-
end
-
-
1
def _normalize_legacy_filter(kind, filter)
-
16
if !filter.respond_to?(kind) && filter.respond_to?(:filter)
-
message = "Filter object with #filter method is deprecated. Define method corresponding " \
-
"to filter type (#before, #after or #around)."
-
ActiveSupport::Deprecation.warn message
-
filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
-
def #{kind}(context, &block) filter(context, &block) end
-
RUBY_EVAL
-
16
elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around)
-
message = "Filter object with #before and #after methods is deprecated. Define #around method instead."
-
ActiveSupport::Deprecation.warn message
-
def filter.around(context)
-
should_continue = before(context)
-
yield if should_continue
-
after(context)
-
end
-
end
-
end
-
end
-
-
# An Array with a compile method.
-
1
class CallbackChain < Array #:nodoc:#
-
1
attr_reader :name, :config
-
-
1
def initialize(name, config)
-
23
@name = name
-
23
@config = {
-
:terminator => "false",
-
:scope => [ :kind ]
-
}.merge(config)
-
end
-
-
1
def compile
-
913
method = []
-
913
method << "value = nil"
-
913
method << "halted = false"
-
-
913
callbacks = "value = !halted && (!block_given? || yield)"
-
913
reverse_each do |callback|
-
828
callbacks = callback.apply(callbacks)
-
end
-
913
method << callbacks
-
-
913
method << "value"
-
913
method.join("\n")
-
end
-
-
end
-
-
1
module ClassMethods
-
-
# This method defines callback chain method for the given kind
-
# if it was not yet defined.
-
# This generated method plays caching role.
-
1
def __define_callbacks(kind, object) #:nodoc:
-
9754
name = __callback_runner_name(kind)
-
9754
unless object.respond_to?(name, true)
-
913
str = object.send("_#{kind}_callbacks").compile
-
913
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
-
def #{name}() #{str} end
-
protected :#{name}
-
RUBY_EVAL
-
end
-
9754
name
-
end
-
-
1
def __reset_runner(symbol)
-
195
name = __callback_runner_name(symbol)
-
195
undef_method(name) if method_defined?(name)
-
end
-
-
1
def __callback_runner_name(kind)
-
9949
"_run__#{self.name.hash.abs}__#{kind}__callbacks"
-
end
-
-
# This is used internally to append, prepend and skip callbacks to the
-
# CallbackChain.
-
1
def __update_callbacks(name, filters = [], block = nil) #:nodoc:
-
193
type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
-
193
options = filters.last.is_a?(Hash) ? filters.pop : {}
-
193
filters.unshift(block) if block
-
-
193
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
-
193
chain = target.send("_#{name}_callbacks")
-
193
yield target, chain.dup, type, filters, options
-
193
target.__reset_runner(name)
-
end
-
end
-
-
# Install a callback for the given event.
-
#
-
# set_callback :save, :before, :before_meth
-
# set_callback :save, :after, :after_meth, if: :condition
-
# set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff }
-
#
-
# The second arguments indicates whether the callback is to be run +:before+,
-
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
-
# means the first example above can also be written as:
-
#
-
# set_callback :save, :before_meth
-
#
-
# The callback can specified as a symbol naming an instance method; as a
-
# proc, lambda, or block; as a string to be instance evaluated; or as an
-
# object that responds to a certain method determined by the <tt>:scope</tt>
-
# argument to +define_callback+.
-
#
-
# If a proc, lambda, or block is given, its body is evaluated in the context
-
# of the current object. It can also optionally accept the current object as
-
# an argument.
-
#
-
# Before and around callbacks are called in the order that they are set;
-
# after callbacks are called in the reverse order.
-
#
-
# Around callbacks can access the return value from the event, if it
-
# wasn't halted, from the +yield+ call.
-
#
-
# ===== Options
-
#
-
# * <tt>:if</tt> - A symbol naming an instance method or a proc; the
-
# callback will be called only when it returns a +true+ value.
-
# * <tt>:unless</tt> - A symbol naming an instance method or a proc; the
-
# callback will be called only when it returns a +false+ value.
-
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
-
# existing chain rather than appended.
-
1
def set_callback(name, *filter_list, &block)
-
180
mapped = nil
-
-
180
__update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
-
mapped ||= filters.map do |filter|
-
180
Callback.new(chain, filter, type, options.dup, self)
-
180
end
-
-
180
filters.each do |filter|
-
375
chain.delete_if {|c| c.matches?(type, filter) }
-
end
-
-
180
options[:prepend] ? chain.unshift(*(mapped.reverse)) : chain.push(*mapped)
-
-
180
target.send("_#{name}_callbacks=", chain)
-
end
-
end
-
-
# Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
-
# <tt>:unless</tt> options may be passed in order to control when the
-
# callback is skipped.
-
#
-
# class Writer < Person
-
# skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
-
# end
-
1
def skip_callback(name, *filter_list, &block)
-
13
__update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
-
13
filters.each do |filter|
-
42
filter = chain.find {|c| c.matches?(type, filter) }
-
-
13
if filter && options.any?
-
5
new_filter = filter.clone(chain, self)
-
5
chain.insert(chain.index(filter), new_filter)
-
5
new_filter.recompile!(options)
-
end
-
-
13
chain.delete(filter)
-
end
-
13
target.send("_#{name}_callbacks=", chain)
-
end
-
end
-
-
# Remove all set callbacks for the given event.
-
1
def reset_callbacks(symbol)
-
2
callbacks = send("_#{symbol}_callbacks")
-
-
2
ActiveSupport::DescendantsTracker.descendants(self).each do |target|
-
chain = target.send("_#{symbol}_callbacks").dup
-
callbacks.each { |c| chain.delete(c) }
-
target.send("_#{symbol}_callbacks=", chain)
-
target.__reset_runner(symbol)
-
end
-
-
2
self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
-
-
2
__reset_runner(symbol)
-
end
-
-
# Define sets of events in the object lifecycle that support callbacks.
-
#
-
# define_callbacks :validate
-
# define_callbacks :initialize, :save, :destroy
-
#
-
# ===== Options
-
#
-
# * <tt>:terminator</tt> - Determines when a before filter will halt the
-
# callback chain, preventing following callbacks from being called and
-
# the event from being triggered. This is a string to be eval'ed. The
-
# result of the callback is available in the +result+ variable.
-
#
-
# define_callbacks :validate, terminator: 'result == false'
-
#
-
# In this example, if any before validate callbacks returns +false+,
-
# other callbacks are not executed. Defaults to +false+, meaning no value
-
# halts the chain.
-
#
-
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
-
# callbacks should be terminated by the <tt>:terminator</tt> option. By
-
# default after callbacks executed no matter if callback chain was
-
# terminated or not. Option makes sense only when <tt>:terminator</tt>
-
# option is specified.
-
#
-
# * <tt>:scope</tt> - Indicates which methods should be executed when an
-
# object is used as a callback.
-
#
-
# class Audit
-
# def before(caller)
-
# puts 'Audit: before is called'
-
# end
-
#
-
# def before_save(caller)
-
# puts 'Audit: before_save is called'
-
# end
-
# end
-
#
-
# class Account
-
# include ActiveSupport::Callbacks
-
#
-
# define_callbacks :save
-
# set_callback :save, :before, Audit.new
-
#
-
# def save
-
# run_callbacks :save do
-
# puts 'save in main'
-
# end
-
# end
-
# end
-
#
-
# In the above case whenever you save an account the method
-
# <tt>Audit#before</tt> will be called. On the other hand
-
#
-
# define_callbacks :save, scope: [:kind, :name]
-
#
-
# would trigger <tt>Audit#before_save</tt> instead. That's constructed
-
# by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
-
# case "kind" is "before" and "name" is "save". In this context +:kind+
-
# and +:name+ have special meanings: +:kind+ refers to the kind of
-
# callback (before/after/around) and +:name+ refers to the method on
-
# which callbacks are being defined.
-
#
-
# A declaration like
-
#
-
# define_callbacks :save, scope: [:name]
-
#
-
# would call <tt>Audit#save</tt>.
-
1
def define_callbacks(*callbacks)
-
20
config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
-
20
callbacks.each do |callback|
-
23
class_attribute "_#{callback}_callbacks"
-
23
send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
# A typical module looks like this:
-
#
-
# module M
-
# def self.included(base)
-
# base.extend ClassMethods
-
# scope :disabled, -> { where(disabled: true) }
-
# end
-
#
-
# module ClassMethods
-
# ...
-
# end
-
# end
-
#
-
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be
-
# written as:
-
#
-
# require 'active_support/concern'
-
#
-
# module M
-
# extend ActiveSupport::Concern
-
#
-
# included do
-
# scope :disabled, -> { where(disabled: true) }
-
# end
-
#
-
# module ClassMethods
-
# ...
-
# end
-
# end
-
#
-
# Moreover, it gracefully handles module dependencies. Given a +Foo+ module
-
# and a +Bar+ module which depends on the former, we would typically write the
-
# following:
-
#
-
# module Foo
-
# def self.included(base)
-
# base.class_eval do
-
# def self.method_injected_by_foo
-
# ...
-
# end
-
# end
-
# end
-
# end
-
#
-
# module Bar
-
# def self.included(base)
-
# base.method_injected_by_foo
-
# end
-
# end
-
#
-
# class Host
-
# include Foo # We need to include this dependency for Bar
-
# include Bar # Bar is the module that Host really needs
-
# end
-
#
-
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
-
# could try to hide these from +Host+ directly including +Foo+ in +Bar+:
-
#
-
# module Bar
-
# include Foo
-
# def self.included(base)
-
# base.method_injected_by_foo
-
# end
-
# end
-
#
-
# class Host
-
# include Bar
-
# end
-
#
-
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
-
# is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
-
# module dependencies are properly resolved:
-
#
-
# require 'active_support/concern'
-
#
-
# module Foo
-
# extend ActiveSupport::Concern
-
# included do
-
# class_eval do
-
# def self.method_injected_by_foo
-
# ...
-
# end
-
# end
-
# end
-
# end
-
#
-
# module Bar
-
# extend ActiveSupport::Concern
-
# include Foo
-
#
-
# included do
-
# self.method_injected_by_foo
-
# end
-
# end
-
#
-
# class Host
-
# include Bar # works, Bar takes care now of its dependencies
-
# end
-
1
module Concern
-
1
def self.extended(base) #:nodoc:
-
409
base.instance_variable_set("@_dependencies", [])
-
end
-
-
1
def append_features(base)
-
4314
if base.instance_variable_defined?("@_dependencies")
-
364
base.instance_variable_get("@_dependencies") << self
-
364
return false
-
else
-
3950
return false if base < self
-
2860
@_dependencies.each { |dep| base.send(:include, dep) }
-
1589
super
-
1589
base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
-
1589
base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
-
end
-
end
-
-
1
def included(base = nil, &block)
-
4682
if base.nil?
-
368
@_included_block = block
-
else
-
4314
super
-
end
-
end
-
end
-
end
-
1
require 'thread'
-
1
require 'monitor'
-
-
1
module ActiveSupport
-
1
module Concurrency
-
1
class Latch
-
1
def initialize(count = 1)
-
4
@count = count
-
4
@lock = Monitor.new
-
4
@cv = @lock.new_cond
-
end
-
-
1
def release
-
3
@lock.synchronize do
-
3
@count -= 1 if @count > 0
-
3
@cv.broadcast if @count.zero?
-
end
-
end
-
-
1
def await
-
3
@lock.synchronize do
-
9
@cv.wait_while { @count > 0 }
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/concern'
-
1
require 'active_support/ordered_options'
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
module ActiveSupport
-
# Configurable provides a <tt>config</tt> method to store and retrieve
-
# configuration options as an <tt>OrderedHash</tt>.
-
1
module Configurable
-
1
extend ActiveSupport::Concern
-
-
1
class Configuration < ActiveSupport::InheritableOptions
-
1
def compile_methods!
-
self.class.compile_methods!(keys)
-
end
-
-
# Compiles reader methods so we don't have to go through method_missing.
-
1
def self.compile_methods!(keys)
-
keys.reject { |m| method_defined?(m) }.each do |key|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{key}; _get(#{key.inspect}); end
-
RUBY
-
end
-
end
-
end
-
-
1
module ClassMethods
-
1
def config
-
@_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config)
-
255
superclass.config.inheritable_copy
-
else
-
# create a new "anonymous" class that will host the compiled reader methods
-
1
Class.new(Configuration).new
-
13850
end
-
end
-
-
1
def configure
-
yield config
-
end
-
-
# Allows you to add shortcut so that you don't have to refer to attribute
-
# through config. Also look at the example for config to contrast.
-
#
-
# Defines both class and instance config accessors.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :allowed_access
-
# end
-
#
-
# User.allowed_access # => nil
-
# User.allowed_access = false
-
# User.allowed_access # => false
-
#
-
# user = User.new
-
# user.allowed_access # => false
-
# user.allowed_access = true
-
# user.allowed_access # => true
-
#
-
# User.allowed_access # => false
-
#
-
# The attribute name must be a valid method name in Ruby.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :"1_Badname"
-
# end
-
# # => NameError: invalid config attribute name
-
#
-
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
-
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :allowed_access, instance_reader: false, instance_writer: false
-
# end
-
#
-
# User.allowed_access = false
-
# User.allowed_access # => false
-
#
-
# User.new.allowed_access = true # => NoMethodError
-
# User.new.allowed_access # => NoMethodError
-
#
-
# Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :allowed_access, instance_accessor: false
-
# end
-
#
-
# User.allowed_access = false
-
# User.allowed_access # => false
-
#
-
# User.new.allowed_access = true # => NoMethodError
-
# User.new.allowed_access # => NoMethodError
-
#
-
# Also you can pass a block to set up the attribute with a default value.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :hair_colors do
-
# [:brown, :black, :blonde, :red]
-
# end
-
# end
-
#
-
# User.hair_colors # => [:brown, :black, :blonde, :red]
-
1
def config_accessor(*names)
-
10
options = names.extract_options!
-
-
10
names.each do |name|
-
20
raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/
-
-
20
reader, reader_line = "def #{name}; config.#{name}; end", __LINE__
-
20
writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__
-
-
20
singleton_class.class_eval reader, __FILE__, reader_line
-
20
singleton_class.class_eval writer, __FILE__, writer_line
-
-
20
unless options[:instance_accessor] == false
-
20
class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false
-
20
class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false
-
end
-
20
send("#{name}=", yield) if block_given?
-
end
-
end
-
end
-
-
# Reads and writes attributes from a configuration <tt>OrderedHash</tt>.
-
#
-
# require 'active_support/configurable'
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# end
-
#
-
# user = User.new
-
#
-
# user.config.allowed_access = true
-
# user.config.level = 1
-
#
-
# user.config.allowed_access # => true
-
# user.config.level # => 1
-
1
def config
-
3133
@_config ||= self.class.config.inheritable_copy
-
end
-
end
-
end
-
-
1
require 'active_support/core_ext/array/wrap'
-
1
require 'active_support/core_ext/array/access'
-
1
require 'active_support/core_ext/array/uniq_by'
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/array/grouping'
-
1
require 'active_support/core_ext/array/prepend_and_append'
-
1
class Array
-
# Returns the tail of the array from +position+.
-
#
-
# %w( a b c d ).from(0) # => ["a", "b", "c", "d"]
-
# %w( a b c d ).from(2) # => ["c", "d"]
-
# %w( a b c d ).from(10) # => []
-
# %w().from(0) # => []
-
1
def from(position)
-
self[position, length] || []
-
end
-
-
# Returns the beginning of the array up to +position+.
-
#
-
# %w( a b c d ).to(0) # => ["a"]
-
# %w( a b c d ).to(2) # => ["a", "b", "c"]
-
# %w( a b c d ).to(10) # => ["a", "b", "c", "d"]
-
# %w().to(0) # => []
-
1
def to(position)
-
first position + 1
-
end
-
-
# Equal to <tt>self[1]</tt>.
-
#
-
# %w( a b c d e).second # => "b"
-
1
def second
-
151
self[1]
-
end
-
-
# Equal to <tt>self[2]</tt>.
-
#
-
# %w( a b c d e).third # => "c"
-
1
def third
-
7
self[2]
-
end
-
-
# Equal to <tt>self[3]</tt>.
-
#
-
# %w( a b c d e).fourth # => "d"
-
1
def fourth
-
self[3]
-
end
-
-
# Equal to <tt>self[4]</tt>.
-
#
-
# %w( a b c d e).fifth # => "e"
-
1
def fifth
-
self[4]
-
end
-
-
# Equal to <tt>self[41]</tt>. Also known as accessing "the reddit".
-
1
def forty_two
-
self[41]
-
end
-
end
-
1
require 'active_support/xml_mini'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/string/inflections'
-
1
require 'active_support/core_ext/object/to_param'
-
1
require 'active_support/core_ext/object/to_query'
-
-
1
class Array
-
# Converts the array to a comma-separated sentence where the last element is
-
# joined by the connector word.
-
#
-
# You can pass the following options to change the default behaviour. If you
-
# pass an option key that doesn't exist in the list below, it will raise an
-
# <tt>ArgumentError</tt>.
-
#
-
# Options:
-
#
-
# * <tt>:words_connector</tt> - The sign or word used to join the elements
-
# in arrays with two or more elements (default: ", ").
-
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements
-
# in arrays with two elements (default: " and ").
-
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element
-
# in arrays with three or more elements (default: ", and ").
-
# * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
-
# the connector options defined on the 'support.array' namespace in the
-
# corresponding dictionary file.
-
#
-
# [].to_sentence # => ""
-
# ['one'].to_sentence # => "one"
-
# ['one', 'two'].to_sentence # => "one and two"
-
# ['one', 'two', 'three'].to_sentence # => "one, two, and three"
-
#
-
# ['one', 'two'].to_sentence(passing: 'invalid option')
-
# # => ArgumentError: Unknown key :passing
-
#
-
# ['one', 'two'].to_sentence(two_words_connector: '-')
-
# # => "one-two"
-
#
-
# ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ')
-
# # => "one or two or at least three"
-
#
-
# Examples using <tt>:locale</tt> option:
-
#
-
# # Given this locale dictionary:
-
# #
-
# # es:
-
# # support:
-
# # array:
-
# # words_connector: " o "
-
# # two_words_connector: " y "
-
# # last_word_connector: " o al menos "
-
#
-
# ['uno', 'dos'].to_sentence(locale: :es)
-
# # => "uno y dos"
-
#
-
# ['uno', 'dos', 'tres'].to_sentence(locale: :es)
-
# # => "uno o dos o al menos tres"
-
1
def to_sentence(options = {})
-
5
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
-
-
5
default_connectors = {
-
:words_connector => ', ',
-
:two_words_connector => ' and ',
-
:last_word_connector => ', and '
-
}
-
5
if defined?(I18n)
-
5
i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
-
5
default_connectors.merge!(i18n_connectors)
-
end
-
5
options = default_connectors.merge!(options)
-
-
5
case length
-
when 0
-
2
''
-
when 1
-
self[0].to_s.dup
-
when 2
-
"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
-
else
-
3
"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
-
end
-
end
-
-
# Converts a collection of elements into a formatted string by calling
-
# <tt>to_s</tt> on all elements and joining them. Having this model:
-
#
-
# class Blog < ActiveRecord::Base
-
# def to_s
-
# title
-
# end
-
# end
-
#
-
# Blog.all.map(&:title) #=> ["First Post", "Second Post", "Third post"]
-
#
-
# <tt>to_formatted_s</tt> shows us:
-
#
-
# Blog.all.to_formatted_s # => "First PostSecond PostThird Post"
-
#
-
# Adding in the <tt>:db</tt> argument as the format yields a comma separated
-
# id list:
-
#
-
# Blog.all.to_formatted_s(:db) # => "1,2,3"
-
1
def to_formatted_s(format = :default)
-
152
case format
-
when :db
-
if empty?
-
'null'
-
else
-
collect { |element| element.id }.join(',')
-
end
-
else
-
152
to_default_s
-
end
-
end
-
1
alias_method :to_default_s, :to_s
-
1
alias_method :to_s, :to_formatted_s
-
-
# Returns a string that represents the array in XML by invoking +to_xml+
-
# on each element. Active Record collections delegate their representation
-
# in XML to this method.
-
#
-
# All elements are expected to respond to +to_xml+, if any of them does
-
# not then an exception is raised.
-
#
-
# The root node reflects the class name of the first element in plural
-
# if all elements belong to the same type and that's not Hash:
-
#
-
# customer.projects.to_xml
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <projects type="array">
-
# <project>
-
# <amount type="decimal">20000.0</amount>
-
# <customer-id type="integer">1567</customer-id>
-
# <deal-date type="date">2008-04-09</deal-date>
-
# ...
-
# </project>
-
# <project>
-
# <amount type="decimal">57230.0</amount>
-
# <customer-id type="integer">1567</customer-id>
-
# <deal-date type="date">2008-04-15</deal-date>
-
# ...
-
# </project>
-
# </projects>
-
#
-
# Otherwise the root element is "objects":
-
#
-
# [{ foo: 1, bar: 2}, { baz: 3}].to_xml
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <objects type="array">
-
# <object>
-
# <bar type="integer">2</bar>
-
# <foo type="integer">1</foo>
-
# </object>
-
# <object>
-
# <baz type="integer">3</baz>
-
# </object>
-
# </objects>
-
#
-
# If the collection is empty the root element is "nil-classes" by default:
-
#
-
# [].to_xml
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <nil-classes type="array"/>
-
#
-
# To ensure a meaningful root element use the <tt>:root</tt> option:
-
#
-
# customer_with_no_projects.projects.to_xml(root: 'projects')
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <projects type="array"/>
-
#
-
# By default name of the node for the children of root is <tt>root.singularize</tt>.
-
# You can change it with the <tt>:children</tt> option.
-
#
-
# The +options+ hash is passed downwards:
-
#
-
# Message.all.to_xml(skip_types: true)
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <messages>
-
# <message>
-
# <created-at>2008-03-07T09:58:18+01:00</created-at>
-
# <id>1</id>
-
# <name>1</name>
-
# <updated-at>2008-03-07T09:58:18+01:00</updated-at>
-
# <user-id>1</user-id>
-
# </message>
-
# </messages>
-
#
-
1
def to_xml(options = {})
-
1
require 'active_support/builder' unless defined?(Builder)
-
-
1
options = options.dup
-
1
options[:indent] ||= 2
-
1
options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
-
1
options[:root] ||= \
-
2
if first.class != Hash && all? { |e| e.is_a?(first.class) }
-
1
underscored = ActiveSupport::Inflector.underscore(first.class.name)
-
1
ActiveSupport::Inflector.pluralize(underscored).tr('/', '_')
-
else
-
'objects'
-
end
-
-
1
builder = options[:builder]
-
1
builder.instruct! unless options.delete(:skip_instruct)
-
-
1
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
-
1
children = options.delete(:children) || root.singularize
-
1
attributes = options[:skip_types] ? {} : { type: 'array' }
-
-
1
if empty?
-
builder.tag!(root, attributes)
-
else
-
1
builder.tag!(root, attributes) do
-
3
each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
-
1
yield builder if block_given?
-
end
-
end
-
end
-
end
-
1
class Hash
-
# By default, only instances of Hash itself are extractable.
-
# Subclasses of Hash may implement this method and return
-
# true to declare themselves as extractable. If a Hash
-
# is extractable, Array#extract_options! pops it from
-
# the Array when it is the last element of the Array.
-
1
def extractable_options?
-
4453
instance_of?(Hash)
-
end
-
end
-
-
1
class Array
-
# Extracts options from a set of arguments. Removes and returns the last
-
# element in the array if it's a hash, otherwise returns a blank hash.
-
#
-
# def options(*args)
-
# args.extract_options!
-
# end
-
#
-
# options(1, 2) # => {}
-
# options(1, 2, a: :b) # => {:a=>:b}
-
1
def extract_options!
-
12728
if last.is_a?(Hash) && last.extractable_options?
-
4449
pop
-
else
-
8279
{}
-
end
-
end
-
end
-
1
class Array
-
# Splits or iterates over the array in groups of size +number+,
-
# padding any remaining slots with +fill_with+ unless it is +false+.
-
#
-
# %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group}
-
# ["1", "2", "3"]
-
# ["4", "5", "6"]
-
# ["7", "8", "9"]
-
# ["10", nil, nil]
-
#
-
# %w(1 2 3 4 5).in_groups_of(2, ' ') {|group| p group}
-
# ["1", "2"]
-
# ["3", "4"]
-
# ["5", " "]
-
#
-
# %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group}
-
# ["1", "2"]
-
# ["3", "4"]
-
# ["5"]
-
1
def in_groups_of(number, fill_with = nil)
-
if fill_with == false
-
collection = self
-
else
-
# size % number gives how many extra we have;
-
# subtracting from number gives how many to add;
-
# modulo number ensures we don't add group of just fill.
-
padding = (number - size % number) % number
-
collection = dup.concat([fill_with] * padding)
-
end
-
-
if block_given?
-
collection.each_slice(number) { |slice| yield(slice) }
-
else
-
groups = []
-
collection.each_slice(number) { |group| groups << group }
-
groups
-
end
-
end
-
-
# Splits or iterates over the array in +number+ of groups, padding any
-
# remaining slots with +fill_with+ unless it is +false+.
-
#
-
# %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
-
# ["1", "2", "3", "4"]
-
# ["5", "6", "7", nil]
-
# ["8", "9", "10", nil]
-
#
-
# %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, ' ') {|group| p group}
-
# ["1", "2", "3", "4"]
-
# ["5", "6", "7", " "]
-
# ["8", "9", "10", " "]
-
#
-
# %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
-
# ["1", "2", "3"]
-
# ["4", "5"]
-
# ["6", "7"]
-
1
def in_groups(number, fill_with = nil)
-
# size / number gives minor group size;
-
# size % number gives how many objects need extra accommodation;
-
# each group hold either division or division + 1 items.
-
division = size / number
-
modulo = size % number
-
-
# create a new array avoiding dup
-
groups = []
-
start = 0
-
-
number.times do |index|
-
length = division + (modulo > 0 && modulo > index ? 1 : 0)
-
padding = fill_with != false &&
-
modulo > 0 && length == division ? 1 : 0
-
groups << slice(start, length).concat([fill_with] * padding)
-
start += length
-
end
-
-
if block_given?
-
groups.each { |g| yield(g) }
-
else
-
groups
-
end
-
end
-
-
# Divides the array into one or more subarrays based on a delimiting +value+
-
# or the result of an optional block.
-
#
-
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
-
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
-
1
def split(value = nil, &block)
-
inject([[]]) do |results, element|
-
if block && block.call(element) || value == element
-
results << []
-
else
-
results.last << element
-
end
-
-
results
-
end
-
end
-
end
-
1
class Array
-
# The human way of thinking about adding stuff to the end of a list is with append
-
1
alias_method :append, :<<
-
-
# The human way of thinking about adding stuff to the beginning of a list is with prepend
-
1
alias_method :prepend, :unshift
-
end
-
1
class Array
-
# *DEPRECATED*: Use +Array#uniq+ instead.
-
#
-
# Returns a unique array based on the criteria in the block.
-
#
-
# [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2]
-
1
def uniq_by(&block)
-
ActiveSupport::Deprecation.warn 'uniq_by is deprecated. Use Array#uniq instead'
-
uniq(&block)
-
end
-
-
# *DEPRECATED*: Use +Array#uniq!+ instead.
-
#
-
# Same as +uniq_by+, but modifies +self+.
-
1
def uniq_by!(&block)
-
ActiveSupport::Deprecation.warn 'uniq_by! is deprecated. Use Array#uniq! instead'
-
uniq!(&block)
-
end
-
end
-
1
class Array
-
# Wraps its argument in an array unless it is already an array (or array-like).
-
#
-
# Specifically:
-
#
-
# * If the argument is +nil+ an empty list is returned.
-
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
-
# * Otherwise, returns an array with the argument as its single element.
-
#
-
# Array.wrap(nil) # => []
-
# Array.wrap([1, 2, 3]) # => [1, 2, 3]
-
# Array.wrap(0) # => [0]
-
#
-
# This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
-
#
-
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
-
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
-
# such a +nil+ right away.
-
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
-
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
-
# * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
-
#
-
# The last point is particularly worth comparing for some enumerables:
-
#
-
# Array(foo: :bar) # => [[:foo, :bar]]
-
# Array.wrap(foo: :bar) # => [{:foo=>:bar}]
-
#
-
# There's also a related idiom that uses the splat operator:
-
#
-
# [*object]
-
#
-
# which for +nil+ returns <tt>[nil]</tt> (Ruby 1.8.7) or <tt>[]</tt> (Ruby
-
# 1.9), and calls to <tt>Array(object)</tt> otherwise.
-
#
-
# Thus, in this case the behavior may be different for +nil+, and the differences with
-
# <tt>Kernel#Array</tt> explained above apply to the rest of <tt>object</tt>s.
-
1
def self.wrap(object)
-
231
if object.nil?
-
24
[]
-
207
elsif object.respond_to?(:to_ary)
-
48
object.to_ary || [object]
-
else
-
159
[object]
-
end
-
end
-
end
-
1
require 'benchmark'
-
-
1
class << Benchmark
-
1
def ms
-
2114
1000 * realtime { yield }
-
end
-
end
-
1
require 'bigdecimal'
-
1
require 'yaml'
-
-
1
class BigDecimal
-
1
YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
-
-
1
def encode_with(coder)
-
string = to_s
-
coder.represent_scalar(nil, YAML_MAPPING[string] || string)
-
end
-
-
# Backport this method if it doesn't exist
-
1
unless method_defined?(:to_d)
-
def to_d
-
self
-
end
-
end
-
-
1
DEFAULT_STRING_FORMAT = 'F'
-
1
def to_formatted_s(*args)
-
2
if args[0].is_a?(Symbol)
-
super
-
else
-
2
format = args[0] || DEFAULT_STRING_FORMAT
-
2
_original_to_s(format)
-
end
-
end
-
1
alias_method :_original_to_s, :to_s
-
1
alias_method :to_s, :to_formatted_s
-
end
-
1
require 'active_support/core_ext/class/attribute'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/class/delegating_attributes'
-
1
require 'active_support/core_ext/class/subclasses'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
class Class
-
# Declare a class-level attribute whose value is inheritable by subclasses.
-
# Subclasses can change their own value and it will not impact parent class.
-
#
-
# class Base
-
# class_attribute :setting
-
# end
-
#
-
# class Subclass < Base
-
# end
-
#
-
# Base.setting = true
-
# Subclass.setting # => true
-
# Subclass.setting = false
-
# Subclass.setting # => false
-
# Base.setting # => true
-
#
-
# In the above case as long as Subclass does not assign a value to setting
-
# by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
-
# would read value assigned to parent class. Once Subclass assigns a value then
-
# the value assigned by Subclass would be returned.
-
#
-
# This matches normal Ruby method inheritance: think of writing an attribute
-
# on a subclass as overriding the reader method. However, you need to be aware
-
# when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
-
# In such cases, you don't want to do changes in places but use setters:
-
#
-
# Base.setting = []
-
# Base.setting # => []
-
# Subclass.setting # => []
-
#
-
# # Appending in child changes both parent and child because it is the same object:
-
# Subclass.setting << :foo
-
# Base.setting # => [:foo]
-
# Subclass.setting # => [:foo]
-
#
-
# # Use setters to not propagate changes:
-
# Base.setting = []
-
# Subclass.setting += [:foo]
-
# Base.setting # => []
-
# Subclass.setting # => [:foo]
-
#
-
# For convenience, a query method is defined as well:
-
#
-
# Subclass.setting? # => false
-
#
-
# Instances may overwrite the class value in the same way:
-
#
-
# Base.setting = true
-
# object = Base.new
-
# object.setting # => true
-
# object.setting = false
-
# object.setting # => false
-
# Base.setting # => true
-
#
-
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
-
#
-
# object.setting # => NoMethodError
-
# object.setting? # => NoMethodError
-
#
-
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
-
#
-
# object.setting = false # => NoMethodError
-
#
-
# To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
-
1
def class_attribute(*attrs)
-
353
options = attrs.extract_options!
-
353
instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
-
353
instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
-
-
353
attrs.each do |name|
-
361
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def self.#{name}() nil end
-
def self.#{name}?() !!#{name} end
-
-
def self.#{name}=(val)
-
singleton_class.class_eval do
-
remove_possible_method(:#{name})
-
define_method(:#{name}) { val }
-
end
-
-
if singleton_class?
-
class_eval do
-
remove_possible_method(:#{name})
-
def #{name}
-
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
-
end
-
end
-
end
-
val
-
end
-
-
if instance_reader
-
remove_possible_method :#{name}
-
def #{name}
-
defined?(@#{name}) ? @#{name} : self.class.#{name}
-
end
-
-
def #{name}?
-
!!#{name}
-
end
-
end
-
RUBY
-
-
361
attr_writer name if instance_writer
-
end
-
end
-
-
1
private
-
1
def singleton_class?
-
2188
ancestors.first != self
-
end
-
end
-
1
require 'active_support/core_ext/array/extract_options'
-
-
# Extends the class object with class and instance accessors for class attributes,
-
# just like the native attr* accessors for instance attributes.
-
1
class Class
-
# Defines a class attribute if it's not defined and creates a reader method that
-
# returns the attribute value.
-
#
-
# class Person
-
# cattr_reader :hair_colors
-
# end
-
#
-
# Person.class_variable_set("@@hair_colors", [:brown, :black])
-
# Person.hair_colors # => [:brown, :black]
-
# Person.new.hair_colors # => [:brown, :black]
-
#
-
# The attribute name must be a valid method name in Ruby.
-
#
-
# class Person
-
# cattr_reader :"1_Badname "
-
# end
-
# # => NameError: invalid attribute name
-
#
-
# If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt>
-
# or <tt>instance_accessor: false</tt>.
-
#
-
# class Person
-
# cattr_reader :hair_colors, instance_reader: false
-
# end
-
#
-
# Person.new.hair_colors # => NoMethodError
-
1
def cattr_reader(*syms)
-
27
options = syms.extract_options!
-
27
syms.each do |sym|
-
28
raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
-
28
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
unless defined? @@#{sym}
-
@@#{sym} = nil
-
end
-
-
def self.#{sym}
-
@@#{sym}
-
end
-
EOS
-
-
28
unless options[:instance_reader] == false || options[:instance_accessor] == false
-
27
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def #{sym}
-
@@#{sym}
-
end
-
EOS
-
end
-
end
-
end
-
-
# Defines a class attribute if it's not defined and creates a writer method to allow
-
# assignment to the attribute.
-
#
-
# class Person
-
# cattr_writer :hair_colors
-
# end
-
#
-
# Person.hair_colors = [:brown, :black]
-
# Person.class_variable_get("@@hair_colors") # => [:brown, :black]
-
# Person.new.hair_colors = [:blonde, :red]
-
# Person.class_variable_get("@@hair_colors") # => [:blonde, :red]
-
#
-
# The attribute name must be a valid method name in Ruby.
-
#
-
# class Person
-
# cattr_writer :"1_Badname "
-
# end
-
# # => NameError: invalid attribute name
-
#
-
# If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt>
-
# or <tt>instance_accessor: false</tt>.
-
#
-
# class Person
-
# cattr_writer :hair_colors, instance_writer: false
-
# end
-
#
-
# Person.new.hair_colors = [:blonde, :red] # => NoMethodError
-
#
-
# Also, you can pass a block to set up the attribute with a default value.
-
#
-
# class Person
-
# cattr_writer :hair_colors do
-
# [:brown, :black, :blonde, :red]
-
# end
-
# end
-
#
-
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
-
1
def cattr_writer(*syms)
-
24
options = syms.extract_options!
-
24
syms.each do |sym|
-
25
raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
-
25
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
unless defined? @@#{sym}
-
@@#{sym} = nil
-
end
-
-
def self.#{sym}=(obj)
-
@@#{sym} = obj
-
end
-
EOS
-
-
25
unless options[:instance_writer] == false || options[:instance_accessor] == false
-
23
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def #{sym}=(obj)
-
@@#{sym} = obj
-
end
-
EOS
-
end
-
25
send("#{sym}=", yield) if block_given?
-
end
-
end
-
-
# Defines both class and instance accessors for class attributes.
-
#
-
# class Person
-
# cattr_accessor :hair_colors
-
# end
-
#
-
# Person.hair_colors = [:brown, :black, :blonde, :red]
-
# Person.hair_colors # => [:brown, :black, :blonde, :red]
-
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
-
#
-
# If a subclass changes the value then that would also change the value for
-
# parent class. Similarly if parent class changes the value then that would
-
# change the value of subclasses too.
-
#
-
# class Male < Person
-
# end
-
#
-
# Male.hair_colors << :blue
-
# Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
-
#
-
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
-
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
-
#
-
# class Person
-
# cattr_accessor :hair_colors, instance_writer: false, instance_reader: false
-
# end
-
#
-
# Person.new.hair_colors = [:brown] # => NoMethodError
-
# Person.new.hair_colors # => NoMethodError
-
#
-
# Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
-
#
-
# class Person
-
# cattr_accessor :hair_colors, instance_accessor: false
-
# end
-
#
-
# Person.new.hair_colors = [:brown] # => NoMethodError
-
# Person.new.hair_colors # => NoMethodError
-
#
-
# Also you can pass a block to set up the attribute with a default value.
-
#
-
# class Person
-
# cattr_accessor :hair_colors do
-
# [:brown, :black, :blonde, :red]
-
# end
-
# end
-
#
-
# Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
-
1
def cattr_accessor(*syms, &blk)
-
24
cattr_reader(*syms)
-
24
cattr_writer(*syms, &blk)
-
end
-
end
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/module/remove_method'
-
-
1
class Class
-
1
def superclass_delegating_accessor(name, options = {})
-
# Create private _name and _name= methods that can still be used if the public
-
# methods are overridden. This allows
-
_superclass_delegating_accessor("_#{name}")
-
-
# Generate the public methods name, name=, and name?
-
# These methods dispatch to the private _name, and _name= methods, making them
-
# overridable
-
singleton_class.send(:define_method, name) { send("_#{name}") }
-
singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") }
-
singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
-
-
# If an instance_reader is needed, generate methods for name and name= on the
-
# class itself, so instances will be able to see them
-
define_method(name) { send("_#{name}") } if options[:instance_reader] != false
-
define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
-
end
-
-
1
private
-
# Take the object being set and store it in a method. This gives us automatic
-
# inheritance behavior, without having to store the object in an instance
-
# variable and look up the superclass chain manually.
-
1
def _stash_object_in_method(object, method, instance_reader = true)
-
singleton_class.remove_possible_method(method)
-
singleton_class.send(:define_method, method) { object }
-
remove_possible_method(method)
-
define_method(method) { object } if instance_reader
-
end
-
-
1
def _superclass_delegating_accessor(name, options = {})
-
singleton_class.send(:define_method, "#{name}=") do |value|
-
_stash_object_in_method(value, name, options[:instance_reader] != false)
-
end
-
send("#{name}=", nil)
-
end
-
end
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/core_ext/module/reachable'
-
-
1
class Class
-
1
begin
-
1
ObjectSpace.each_object(Class.new) {}
-
-
1
def descendants # :nodoc:
-
descendants = []
-
ObjectSpace.each_object(singleton_class) do |k|
-
descendants.unshift k unless k == self
-
end
-
descendants
-
end
-
rescue StandardError # JRuby
-
def descendants # :nodoc:
-
descendants = []
-
ObjectSpace.each_object(Class) do |k|
-
descendants.unshift k if k < self
-
end
-
descendants.uniq!
-
descendants
-
end
-
end
-
-
# Returns an array with the direct children of +self+.
-
#
-
# Integer.subclasses # => [Fixnum, Bignum]
-
#
-
# class Foo; end
-
# class Bar < Foo; end
-
# class Baz < Foo; end
-
#
-
# Foo.subclasses # => [Baz, Bar]
-
1
def subclasses
-
subclasses, chain = [], descendants
-
chain.each do |k|
-
subclasses << k unless chain.any? { |c| c > k }
-
end
-
subclasses
-
end
-
end
-
1
require 'active_support/core_ext/object/acts_like'
-
-
1
class Date
-
# Duck-types as a Date-like class. See Object#acts_like?.
-
1
def acts_like_date?
-
true
-
end
-
end
-
1
require 'date'
-
1
require 'active_support/duration'
-
1
require 'active_support/core_ext/object/acts_like'
-
1
require 'active_support/core_ext/date/zones'
-
1
require 'active_support/core_ext/time/zones'
-
1
require 'active_support/core_ext/date_and_time/calculations'
-
-
1
class Date
-
1
include DateAndTime::Calculations
-
-
1
@beginning_of_week_default = nil
-
-
1
class << self
-
1
attr_accessor :beginning_of_week_default
-
-
# Returns the week start (e.g. :monday) for the current request, if this has been set (via Date.beginning_of_week=).
-
# If <tt>Date.beginning_of_week</tt> has not been set for the current request, returns the week start specified in <tt>config.beginning_of_week</tt>.
-
# If no config.beginning_of_week was specified, returns :monday.
-
1
def beginning_of_week
-
Thread.current[:beginning_of_week] || beginning_of_week_default || :monday
-
end
-
-
# Sets <tt>Date.beginning_of_week</tt> to a week start (e.g. :monday) for current request/thread.
-
#
-
# This method accepts any of the following day symbols:
-
# :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
-
1
def beginning_of_week=(week_start)
-
Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start)
-
end
-
-
# Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol.
-
1
def find_beginning_of_week!(week_start)
-
raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start)
-
week_start
-
end
-
-
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
-
1
def yesterday
-
::Date.current.yesterday
-
end
-
-
# Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
-
1
def tomorrow
-
::Date.current.tomorrow
-
end
-
-
# Returns Time.zone.today when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns Date.today.
-
1
def current
-
::Time.zone ? ::Time.zone.today : ::Date.today
-
end
-
end
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
-
# and then subtracts the specified number of seconds.
-
1
def ago(seconds)
-
to_time_in_current_zone.since(-seconds)
-
end
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
-
# and then adds the specified number of seconds
-
1
def since(seconds)
-
to_time_in_current_zone.since(seconds)
-
end
-
1
alias :in :since
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
-
1
def beginning_of_day
-
to_time_in_current_zone
-
end
-
1
alias :midnight :beginning_of_day
-
1
alias :at_midnight :beginning_of_day
-
1
alias :at_beginning_of_day :beginning_of_day
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
-
1
def end_of_day
-
to_time_in_current_zone.end_of_day
-
end
-
-
1
def plus_with_duration(other) #:nodoc:
-
98
if ActiveSupport::Duration === other
-
other.since(self)
-
else
-
98
plus_without_duration(other)
-
end
-
end
-
1
alias_method :plus_without_duration, :+
-
1
alias_method :+, :plus_with_duration
-
-
1
def minus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
plus_with_duration(-other)
-
else
-
minus_without_duration(other)
-
end
-
end
-
1
alias_method :minus_without_duration, :-
-
1
alias_method :-, :minus_with_duration
-
-
# Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
-
# any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
-
1
def advance(options)
-
253
options = options.dup
-
253
d = self
-
253
d = d >> options.delete(:years) * 12 if options[:years]
-
253
d = d >> options.delete(:months) if options[:months]
-
253
d = d + options.delete(:weeks) * 7 if options[:weeks]
-
253
d = d + options.delete(:days) if options[:days]
-
253
d
-
end
-
-
# Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
-
# The +options+ parameter is a hash with a combination of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>.
-
#
-
# Date.new(2007, 5, 12).change(day: 1) # => Date.new(2007, 5, 1)
-
# Date.new(2007, 5, 12).change(year: 2005, month: 1) # => Date.new(2005, 1, 12)
-
1
def change(options)
-
6
::Date.new(
-
options.fetch(:year, year),
-
options.fetch(:month, month),
-
options.fetch(:day, day)
-
)
-
end
-
end
-
1
require 'date'
-
1
require 'active_support/inflector/methods'
-
1
require 'active_support/core_ext/date/zones'
-
1
require 'active_support/core_ext/module/remove_method'
-
-
1
class Date
-
1
DATE_FORMATS = {
-
:short => '%e %b',
-
:long => '%B %e, %Y',
-
:db => '%Y-%m-%d',
-
:number => '%Y%m%d',
-
:long_ordinal => lambda { |date|
-
day_format = ActiveSupport::Inflector.ordinalize(date.day)
-
date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
-
},
-
:rfc822 => '%e %b %Y'
-
}
-
-
# Ruby 1.9 has Date#to_time which converts to localtime only.
-
1
remove_possible_method :to_time
-
-
# Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
-
1
remove_possible_method :xmlschema
-
-
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
-
#
-
# This method is aliased to <tt>to_s</tt>.
-
#
-
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
-
#
-
# date.to_formatted_s(:db) # => "2007-11-10"
-
# date.to_s(:db) # => "2007-11-10"
-
#
-
# date.to_formatted_s(:short) # => "10 Nov"
-
# date.to_formatted_s(:long) # => "November 10, 2007"
-
# date.to_formatted_s(:long_ordinal) # => "November 10th, 2007"
-
# date.to_formatted_s(:rfc822) # => "10 Nov 2007"
-
#
-
# == Adding your own time formats to to_formatted_s
-
# You can add your own formats to the Date::DATE_FORMATS hash.
-
# Use the format name as the hash key and either a strftime string
-
# or Proc instance that takes a date argument as the value.
-
#
-
# # config/initializers/time_formats.rb
-
# Date::DATE_FORMATS[:month_and_year] = '%B %Y'
-
# Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") }
-
1
def to_formatted_s(format = :default)
-
if formatter = DATE_FORMATS[format]
-
if formatter.respond_to?(:call)
-
formatter.call(self).to_s
-
else
-
strftime(formatter)
-
end
-
else
-
to_default_s
-
end
-
end
-
1
alias_method :to_default_s, :to_s
-
1
alias_method :to_s, :to_formatted_s
-
-
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
-
1
def readable_inspect
-
strftime('%a, %d %b %Y')
-
end
-
1
alias_method :default_inspect, :inspect
-
1
alias_method :inspect, :readable_inspect
-
-
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
-
# The timezone can be either :local or :utc (default :local).
-
#
-
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
-
#
-
# date.to_time # => Sat Nov 10 00:00:00 0800 2007
-
# date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007
-
#
-
# date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
-
1
def to_time(form = :local)
-
6
::Time.send("#{form}_time", year, month, day)
-
end
-
-
1
def xmlschema
-
to_time_in_current_zone.xmlschema
-
end
-
end
-
1
require 'date'
-
1
require 'active_support/core_ext/time/zones'
-
-
1
class Date
-
# Converts Date to a TimeWithZone in the current zone if <tt>Time.zone</tt> or
-
# <tt>Time.zone_default</tt> is set, otherwise converts Date to a Time via
-
# Date#to_time.
-
1
def to_time_in_current_zone
-
if ::Time.zone
-
::Time.zone.local(year, month, day)
-
else
-
to_time
-
end
-
end
-
end
-
1
module DateAndTime
-
1
module Calculations
-
1
DAYS_INTO_WEEK = {
-
:monday => 0,
-
:tuesday => 1,
-
:wednesday => 2,
-
:thursday => 3,
-
:friday => 4,
-
:saturday => 5,
-
:sunday => 6
-
}
-
-
# Returns a new date/time representing yesterday.
-
1
def yesterday
-
advance(:days => -1)
-
end
-
-
# Returns a new date/time representing tomorrow.
-
1
def tomorrow
-
advance(:days => 1)
-
end
-
-
# Returns true if the date/time is today.
-
1
def today?
-
to_date == ::Date.current
-
end
-
-
# Returns true if the date/time is in the past.
-
1
def past?
-
self < self.class.current
-
end
-
-
# Returns true if the date/time is in the future.
-
1
def future?
-
self > self.class.current
-
end
-
-
# Returns a new date/time the specified number of days ago.
-
1
def days_ago(days)
-
advance(:days => -days)
-
end
-
-
# Returns a new date/time the specified number of days in the future.
-
1
def days_since(days)
-
advance(:days => days)
-
end
-
-
# Returns a new date/time the specified number of weeks ago.
-
1
def weeks_ago(weeks)
-
advance(:weeks => -weeks)
-
end
-
-
# Returns a new date/time the specified number of weeks in the future.
-
1
def weeks_since(weeks)
-
advance(:weeks => weeks)
-
end
-
-
# Returns a new date/time the specified number of months ago.
-
1
def months_ago(months)
-
advance(:months => -months)
-
end
-
-
# Returns a new date/time the specified number of months in the future.
-
1
def months_since(months)
-
advance(:months => months)
-
end
-
-
# Returns a new date/time the specified number of years ago.
-
1
def years_ago(years)
-
advance(:years => -years)
-
end
-
-
# Returns a new date/time the specified number of years in the future.
-
1
def years_since(years)
-
advance(:years => years)
-
end
-
-
# Returns a new date/time at the start of the month.
-
# DateTime objects will have a time set to 0:00.
-
1
def beginning_of_month
-
first_hour{ change(:day => 1) }
-
end
-
1
alias :at_beginning_of_month :beginning_of_month
-
-
# Returns a new date/time at the start of the quarter.
-
# Example: 1st January, 1st July, 1st October.
-
# DateTime objects will have a time set to 0:00.
-
1
def beginning_of_quarter
-
first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
-
beginning_of_month.change(:month => first_quarter_month)
-
end
-
1
alias :at_beginning_of_quarter :beginning_of_quarter
-
-
# Returns a new date/time at the end of the quarter.
-
# Example: 31st March, 30th June, 30th September.
-
# DateTIme objects will have a time set to 23:59:59.
-
1
def end_of_quarter
-
last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
-
beginning_of_month.change(:month => last_quarter_month).end_of_month
-
end
-
1
alias :at_end_of_quarter :end_of_quarter
-
-
# Return a new date/time at the beginning of the year.
-
# Example: 1st January.
-
# DateTime objects will have a time set to 0:00.
-
1
def beginning_of_year
-
change(:month => 1).beginning_of_month
-
end
-
1
alias :at_beginning_of_year :beginning_of_year
-
-
# Returns a new date/time representing the given day in the next week.
-
# Week is assumed to start on +start_day+, default is
-
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
-
# DateTime objects have their time set to 0:00.
-
1
def next_week(start_day = Date.beginning_of_week)
-
first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(start_day)) }
-
end
-
-
# Short-hand for months_since(1).
-
1
def next_month
-
months_since(1)
-
end
-
-
# Short-hand for months_since(3)
-
1
def next_quarter
-
months_since(3)
-
end
-
-
# Short-hand for years_since(1).
-
1
def next_year
-
years_since(1)
-
end
-
-
# Returns a new date/time representing the given day in the previous week.
-
# Week is assumed to start on +start_day+, default is
-
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
-
# DateTime objects have their time set to 0:00.
-
1
def prev_week(start_day = Date.beginning_of_week)
-
first_hour{ weeks_ago(1).beginning_of_week.days_since(days_span(start_day)) }
-
end
-
1
alias_method :last_week, :prev_week
-
-
# Short-hand for months_ago(1).
-
1
def prev_month
-
months_ago(1)
-
end
-
1
alias_method :last_month, :prev_month
-
-
# Short-hand for months_ago(3).
-
1
def prev_quarter
-
months_ago(3)
-
end
-
1
alias_method :last_quarter, :prev_quarter
-
-
# Short-hand for years_ago(1).
-
1
def prev_year
-
years_ago(1)
-
end
-
1
alias_method :last_year, :prev_year
-
-
# Returns the number of days to the start of the week on the given day.
-
# Week is assumed to start on +start_day+, default is
-
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
-
1
def days_to_week_start(start_day = Date.beginning_of_week)
-
start_day_number = DAYS_INTO_WEEK[start_day]
-
current_day_number = wday != 0 ? wday - 1 : 6
-
(current_day_number - start_day_number) % 7
-
end
-
-
# Returns a new date/time representing the start of this week on the given day.
-
# Week is assumed to start on +start_day+, default is
-
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
-
# +DateTime+ objects have their time set to 0:00.
-
1
def beginning_of_week(start_day = Date.beginning_of_week)
-
result = days_ago(days_to_week_start(start_day))
-
acts_like?(:time) ? result.midnight : result
-
end
-
1
alias :at_beginning_of_week :beginning_of_week
-
-
# Returns Monday of this week assuming that week starts on Monday.
-
# +DateTime+ objects have their time set to 0:00.
-
1
def monday
-
beginning_of_week(:monday)
-
end
-
-
# Returns a new date/time representing the end of this week on the given day.
-
# Week is assumed to start on +start_day+, default is
-
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
-
# DateTime objects have their time set to 23:59:59.
-
1
def end_of_week(start_day = Date.beginning_of_week)
-
last_hour{ days_since(6 - days_to_week_start(start_day)) }
-
end
-
1
alias :at_end_of_week :end_of_week
-
-
# Returns Sunday of this week assuming that week starts on Monday.
-
# +DateTime+ objects have their time set to 23:59:59.
-
1
def sunday
-
end_of_week(:monday)
-
end
-
-
# Returns a new date/time representing the end of the month.
-
# DateTime objects will have a time set to 23:59:59.
-
1
def end_of_month
-
last_day = ::Time.days_in_month(month, year)
-
last_hour{ days_since(last_day - day) }
-
end
-
1
alias :at_end_of_month :end_of_month
-
-
# Returns a new date/time representing the end of the year.
-
# DateTime objects will have a time set to 23:59:59.
-
1
def end_of_year
-
change(:month => 12).end_of_month
-
end
-
1
alias :at_end_of_year :end_of_year
-
-
1
private
-
-
1
def first_hour
-
result = yield
-
acts_like?(:time) ? result.change(:hour => 0) : result
-
end
-
-
1
def last_hour
-
result = yield
-
acts_like?(:time) ? result.end_of_day : result
-
end
-
-
1
def days_span(day)
-
(DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/acts_like'
-
-
1
class DateTime
-
# Duck-types as a Date-like class. See Object#acts_like?.
-
1
def acts_like_date?
-
true
-
end
-
-
# Duck-types as a Time-like class. See Object#acts_like?.
-
1
def acts_like_time?
-
true
-
end
-
end
-
1
require 'active_support/deprecation'
-
-
1
class DateTime
-
1
class << self
-
# *DEPRECATED*: Use +DateTime.civil_from_format+ directly.
-
1
def local_offset
-
ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. Use DateTime.civil_from_format directly.'
-
-
::Time.local(2012).utc_offset.to_r / 86400
-
end
-
-
# Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or
-
# <tt>config.time_zone</tt> are set, otherwise returns
-
# <tt>Time.now.to_datetime</tt>.
-
1
def current
-
::Time.zone ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
-
end
-
end
-
-
# Tells whether the DateTime object's datetime lies in the past.
-
1
def past?
-
self < ::DateTime.current
-
end
-
-
# Tells whether the DateTime object's datetime lies in the future.
-
1
def future?
-
self > ::DateTime.current
-
end
-
-
# Seconds since midnight: DateTime.now.seconds_since_midnight.
-
1
def seconds_since_midnight
-
sec + (min * 60) + (hour * 3600)
-
end
-
-
# Returns a new DateTime where one or more of the elements have been changed
-
# according to the +options+ parameter. The time options (<tt>:hour</tt>,
-
# <tt>:minute</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is
-
# passed, then minute and sec is set to 0. If the hour and minute is passed,
-
# then sec is set to 0. The +options+ parameter takes a hash with any of these
-
# keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>,
-
# <tt>:min</tt>, <tt>:sec</tt>, <tt>:offset</tt>, <tt>:start</tt>.
-
#
-
# DateTime.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => DateTime.new(2012, 8, 1, 22, 35, 0)
-
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0)
-
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0)
-
1
def change(options)
-
::DateTime.civil(
-
options.fetch(:year, year),
-
options.fetch(:month, month),
-
options.fetch(:day, day),
-
options.fetch(:hour, hour),
-
options.fetch(:min, options[:hour] ? 0 : min),
-
options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec),
-
options.fetch(:offset, offset),
-
options.fetch(:start, start)
-
)
-
end
-
-
# Uses Date to provide precise Time calculations for years, months, and days.
-
# The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
-
# <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
-
# <tt>:minutes</tt>, <tt>:seconds</tt>.
-
1
def advance(options)
-
d = to_date.advance(options)
-
datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
-
seconds_to_advance = \
-
options.fetch(:seconds, 0) +
-
options.fetch(:minutes, 0) * 60 +
-
options.fetch(:hours, 0) * 3600
-
-
if seconds_to_advance.zero?
-
datetime_advanced_by_date
-
else
-
datetime_advanced_by_date.since seconds_to_advance
-
end
-
end
-
-
# Returns a new DateTime representing the time a number of seconds ago.
-
# Do not use this method in combination with x.months, use months_ago instead!
-
1
def ago(seconds)
-
since(-seconds)
-
end
-
-
# Returns a new DateTime representing the time a number of seconds since the
-
# instance time. Do not use this method in combination with x.months, use
-
# months_since instead!
-
1
def since(seconds)
-
self + Rational(seconds.round, 86400)
-
end
-
1
alias :in :since
-
-
# Returns a new DateTime representing the start of the day (0:00).
-
1
def beginning_of_day
-
change(:hour => 0)
-
end
-
1
alias :midnight :beginning_of_day
-
1
alias :at_midnight :beginning_of_day
-
1
alias :at_beginning_of_day :beginning_of_day
-
-
# Returns a new DateTime representing the end of the day (23:59:59).
-
1
def end_of_day
-
change(:hour => 23, :min => 59, :sec => 59)
-
end
-
-
# Returns a new DateTime representing the start of the hour (hh:00:00).
-
1
def beginning_of_hour
-
change(:min => 0)
-
end
-
1
alias :at_beginning_of_hour :beginning_of_hour
-
-
# Returns a new DateTime representing the end of the hour (hh:59:59).
-
1
def end_of_hour
-
change(:min => 59, :sec => 59)
-
end
-
-
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0.
-
#
-
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
-
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
-
1
def utc
-
new_offset(0)
-
end
-
1
alias_method :getutc, :utc
-
-
# Returns +true+ if <tt>offset == 0</tt>.
-
1
def utc?
-
offset == 0
-
end
-
-
# Returns the offset value in seconds.
-
1
def utc_offset
-
(offset * 86400).to_i
-
end
-
-
# Layers additional behavior on DateTime#<=> so that Time and
-
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
-
1
def <=>(other)
-
205
super other.to_datetime
-
end
-
-
end
-
1
require 'active_support/inflector/methods'
-
1
require 'active_support/core_ext/time/conversions'
-
1
require 'active_support/core_ext/date_time/calculations'
-
1
require 'active_support/values/time_zone'
-
-
1
class DateTime
-
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
-
#
-
# This method is aliased to <tt>to_s</tt>.
-
#
-
# === Examples
-
# datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
-
#
-
# datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
-
# datetime.to_s(:db) # => "2007-12-04 00:00:00"
-
# datetime.to_s(:number) # => "20071204000000"
-
# datetime.to_formatted_s(:short) # => "04 Dec 00:00"
-
# datetime.to_formatted_s(:long) # => "December 04, 2007 00:00"
-
# datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00"
-
# datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000"
-
#
-
# == Adding your own datetime formats to to_formatted_s
-
# DateTime formats are shared with Time. You can add your own to the
-
# Time::DATE_FORMATS hash. Use the format name as the hash key and
-
# either a strftime string or Proc instance that takes a time or
-
# datetime argument as the value.
-
#
-
# # config/initializers/time_formats.rb
-
# Time::DATE_FORMATS[:month_and_year] = '%B %Y'
-
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
-
1
def to_formatted_s(format = :default)
-
if formatter = ::Time::DATE_FORMATS[format]
-
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
-
else
-
to_default_s
-
end
-
end
-
1
alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
-
1
alias_method :to_s, :to_formatted_s
-
-
#
-
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
-
# datetime.formatted_offset # => "-06:00"
-
# datetime.formatted_offset(false) # => "-0600"
-
1
def formatted_offset(colon = true, alternate_utc_string = nil)
-
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000".
-
1
def readable_inspect
-
to_s(:rfc822)
-
end
-
1
alias_method :default_inspect, :inspect
-
1
alias_method :inspect, :readable_inspect
-
-
# Returns DateTime with local offset for given year if format is local else
-
# offset is zero.
-
#
-
# DateTime.civil_from_format :local, 2012
-
# # => Sun, 01 Jan 2012 00:00:00 +0300
-
# DateTime.civil_from_format :local, 2012, 12, 17
-
# # => Mon, 17 Dec 2012 00:00:00 +0000
-
1
def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0)
-
if utc_or_local.to_sym == :local
-
offset = ::Time.local(year, month, day).utc_offset.to_r / 86400
-
else
-
offset = 0
-
end
-
civil(year, month, day, hour, min, sec, offset)
-
end
-
-
# Converts +self+ to a floating-point number of seconds since the Unix epoch.
-
1
def to_f
-
seconds_since_unix_epoch.to_f
-
end
-
-
# Converts +self+ to an integer number of seconds since the Unix epoch.
-
1
def to_i
-
seconds_since_unix_epoch.to_i
-
end
-
-
1
private
-
-
1
def offset_in_seconds
-
(offset * 86400).to_i
-
end
-
-
1
def seconds_since_unix_epoch
-
(jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight
-
end
-
end
-
1
require 'active_support/core_ext/time/zones'
-
-
1
class DateTime
-
# Returns the simultaneous time in <tt>Time.zone</tt>.
-
#
-
# Time.zone = 'Hawaii' # => 'Hawaii'
-
# DateTime.new(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt>
-
# as the local zone instead of the operating system's time zone.
-
#
-
# You can also pass in a TimeZone instance or string that identifies a TimeZone
-
# as an argument, and the conversion will be based on that zone instead of
-
# <tt>Time.zone</tt>.
-
#
-
# DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
-
1
def in_time_zone(zone = ::Time.zone)
-
if zone
-
ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
-
else
-
self
-
end
-
end
-
end
-
1
module Enumerable
-
# Calculates a sum from the elements.
-
#
-
# payments.sum { |p| p.price * p.tax_rate }
-
# payments.sum(&:price)
-
#
-
# The latter is a shortcut for:
-
#
-
# payments.inject(0) { |sum, p| sum + p.price }
-
#
-
# It can also calculate the sum without the use of a block.
-
#
-
# [5, 15, 10].sum # => 30
-
# ['foo', 'bar'].sum # => "foobar"
-
# [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5]
-
#
-
# The default sum of an empty list is zero. You can override this default:
-
#
-
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
-
1
def sum(identity = 0, &block)
-
16
if block_given?
-
8
map(&block).sum(identity)
-
else
-
18
inject { |sum, element| sum + element } || identity
-
end
-
end
-
-
# Convert an enumerable to a hash.
-
#
-
# people.index_by(&:login)
-
# => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
-
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
-
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
-
1
def index_by
-
if block_given?
-
Hash[map { |elem| [yield(elem), elem] }]
-
else
-
to_enum :index_by
-
end
-
end
-
-
# Returns +true+ if the enumerable has more than 1 element. Functionally
-
# equivalent to <tt>enum.to_a.size > 1</tt>. Can be called with a block too,
-
# much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns +true+
-
# if more than one person is over 26.
-
1
def many?
-
cnt = 0
-
if block_given?
-
any? do |element|
-
cnt += 1 if yield element
-
cnt > 1
-
end
-
else
-
any? { (cnt += 1) > 1 }
-
end
-
end
-
-
# The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
-
# collection does not include the object.
-
1
def exclude?(object)
-
497
!include?(object)
-
end
-
end
-
-
1
class Range #:nodoc:
-
# Optimize range sum to use arithmetic progression if a block is not given and
-
# we have a range of numeric values.
-
1
def sum(identity = 0)
-
if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
-
super
-
else
-
actual_last = exclude_end? ? (last - 1) : last
-
if actual_last >= first
-
(actual_last - first + 1) * (actual_last + first) / 2
-
else
-
identity
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
FrozenObjectError = RuntimeError
-
end
-
1
require 'fileutils'
-
-
1
class File
-
# Write to a file atomically. Useful for situations where you don't
-
# want other processes or threads to see half-written files.
-
#
-
# File.atomic_write('important.file') do |file|
-
# file.write('hello')
-
# end
-
#
-
# If your temp directory is not on the same filesystem as the file you're
-
# trying to write, you can provide a different temporary directory.
-
#
-
# File.atomic_write('/data/something.important', '/data/tmp') do |file|
-
# file.write('hello')
-
# end
-
1
def self.atomic_write(file_name, temp_dir = Dir.tmpdir)
-
2
require 'tempfile' unless defined?(Tempfile)
-
2
require 'fileutils' unless defined?(FileUtils)
-
-
2
temp_file = Tempfile.new(basename(file_name), temp_dir)
-
2
temp_file.binmode
-
2
yield temp_file
-
2
temp_file.close
-
-
2
if File.exists?(file_name)
-
# Get original file permissions
-
old_stat = stat(file_name)
-
else
-
# If not possible, probe which are the default permissions in the
-
# destination directory.
-
2
old_stat = probe_stat_in(dirname(file_name))
-
end
-
-
# Overwrite original file with temp file
-
2
FileUtils.mv(temp_file.path, file_name)
-
-
# Set correct permissions on new file
-
2
begin
-
2
chown(old_stat.uid, old_stat.gid, file_name)
-
# This operation will affect filesystem ACL's
-
2
chmod(old_stat.mode, file_name)
-
rescue Errno::EPERM
-
# Changing file ownership failed, moving on.
-
end
-
end
-
-
# Private utility method.
-
1
def self.probe_stat_in(dir) #:nodoc:
-
2
basename = [
-
'.permissions_check',
-
Thread.current.object_id,
-
Process.pid,
-
rand(1000000)
-
].join('.')
-
-
2
file_name = join(dir, basename)
-
2
FileUtils.touch(file_name)
-
2
stat(file_name)
-
ensure
-
2
FileUtils.rm_f(file_name) if file_name
-
end
-
end
-
1
require 'active_support/xml_mini'
-
1
require 'active_support/time'
-
1
require 'active_support/core_ext/object/blank'
-
1
require 'active_support/core_ext/object/to_param'
-
1
require 'active_support/core_ext/object/to_query'
-
1
require 'active_support/core_ext/array/wrap'
-
1
require 'active_support/core_ext/hash/reverse_merge'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
class Hash
-
# Returns a string containing an XML representation of its receiver:
-
#
-
# {'foo' => 1, 'bar' => 2}.to_xml
-
# # =>
-
# # <?xml version="1.0" encoding="UTF-8"?>
-
# # <hash>
-
# # <foo type="integer">1</foo>
-
# # <bar type="integer">2</bar>
-
# # </hash>
-
#
-
# To do so, the method loops over the pairs and builds nodes that depend on
-
# the _values_. Given a pair +key+, +value+:
-
#
-
# * If +value+ is a hash there's a recursive call with +key+ as <tt>:root</tt>.
-
#
-
# * If +value+ is an array there's a recursive call with +key+ as <tt>:root</tt>,
-
# and +key+ singularized as <tt>:children</tt>.
-
#
-
# * If +value+ is a callable object it must expect one or two arguments. Depending
-
# on the arity, the callable is invoked with the +options+ hash as first argument
-
# with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
-
# callable can add nodes by using <tt>options[:builder]</tt>.
-
#
-
# 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
-
# # => "<b>foo</b>"
-
#
-
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
-
#
-
# class Foo
-
# def to_xml(options)
-
# options[:builder].bar 'fooing!'
-
# end
-
# end
-
#
-
# { foo: Foo.new }.to_xml(skip_instruct: true)
-
# # => "<hash><bar>fooing!</bar></hash>"
-
#
-
# * Otherwise, a node with +key+ as tag is created with a string representation of
-
# +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added.
-
# Unless the option <tt>:skip_types</tt> exists and is true, an attribute "type" is
-
# added as well according to the following mapping:
-
#
-
# XML_TYPE_NAMES = {
-
# "Symbol" => "symbol",
-
# "Fixnum" => "integer",
-
# "Bignum" => "integer",
-
# "BigDecimal" => "decimal",
-
# "Float" => "float",
-
# "TrueClass" => "boolean",
-
# "FalseClass" => "boolean",
-
# "Date" => "date",
-
# "DateTime" => "dateTime",
-
# "Time" => "dateTime"
-
# }
-
#
-
# By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
-
#
-
# The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can
-
# configure your own builder with the <tt>:builder</tt> option. The method also accepts
-
# options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
-
1
def to_xml(options = {})
-
17
require 'active_support/builder' unless defined?(Builder)
-
-
17
options = options.dup
-
17
options[:indent] ||= 2
-
17
options[:root] ||= 'hash'
-
17
options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
-
-
17
builder = options[:builder]
-
17
builder.instruct! unless options.delete(:skip_instruct)
-
-
17
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
-
-
17
builder.tag!(root) do
-
36
each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) }
-
17
yield builder if block_given?
-
end
-
end
-
-
1
class << self
-
1
def from_xml(xml)
-
24
typecast_xml_value(unrename_keys(ActiveSupport::XmlMini.parse(xml)))
-
end
-
-
1
private
-
1
def typecast_xml_value(value)
-
81
case value
-
when Hash
-
73
if value['type'] == 'array'
-
_, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
-
if entries.nil? || (c = value['__content__'] && c.blank?)
-
[]
-
else
-
case entries # something weird with classes not matching here. maybe singleton methods breaking is_a?
-
when Array
-
entries.collect { |v| typecast_xml_value(v) }
-
when Hash
-
[typecast_xml_value(entries)]
-
else
-
raise "can't typecast #{entries.inspect}"
-
end
-
end
-
elsif value['type'] == 'file' ||
-
73
(value['__content__'] && (value.keys.size == 1 || value['__content__'].present?))
-
36
content = value['__content__']
-
36
if parser = ActiveSupport::XmlMini::PARSING[value['type']]
-
16
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
-
else
-
20
content
-
end
-
37
elsif value['type'] == 'string' && value['nil'] != 'true'
-
''
-
# blank or nil parsed values are represented by nil
-
37
elsif value.blank? || value['nil'] == 'true'
-
nil
-
# If the type is the only element which makes it then
-
# this still makes the value nil, except if type is
-
# a XML node(where type['value'] is a Hash)
-
37
elsif value['type'] && value.size == 1 && !value['type'].is_a?(::Hash)
-
nil
-
else
-
91
xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }]
-
-
# Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
-
# how multipart uploaded files from HTML appear
-
37
xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
-
end
-
when Array
-
10
value.map! { |i| typecast_xml_value(i) }
-
3
value.length > 1 ? value : value.first
-
when String
-
5
value
-
else
-
raise "can't typecast #{value.class.name} - #{value.inspect}"
-
end
-
end
-
-
1
def unrename_keys(params)
-
145
case params
-
when Hash
-
191
Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ]
-
when Array
-
10
params.map { |v| unrename_keys(v) }
-
else
-
69
params
-
end
-
end
-
end
-
end
-
1
class Hash
-
# Returns a new hash with +self+ and +other_hash+ merged recursively.
-
#
-
# h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
-
# h2 = { x: { y: [7,8,9] }, z: 'xyz' }
-
#
-
# h1.deep_merge(h2) #=> {x: {y: [7, 8, 9]}, z: "xyz"}
-
# h2.deep_merge(h1) #=> {x: {y: [4, 5, 6]}, z: [7, 8, 9]}
-
# h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) }
-
# #=> {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}
-
1
def deep_merge(other_hash, &block)
-
3897
dup.deep_merge!(other_hash, &block)
-
end
-
-
# Same as +deep_merge+, but modifies +self+.
-
1
def deep_merge!(other_hash, &block)
-
5412
other_hash.each_pair do |k,v|
-
8216
tv = self[k]
-
8216
if tv.is_a?(Hash) && v.is_a?(Hash)
-
3078
self[k] = tv.deep_merge(v, &block)
-
else
-
5138
self[k] = block && tv ? block.call(k, tv, v) : v
-
end
-
end
-
5412
self
-
end
-
end
-
1
class Hash
-
# Return a hash that includes everything but the given keys. This is useful for
-
# limiting a set of parameters to everything but a few known toggles:
-
#
-
# @person.update_attributes(params[:person].except(:admin))
-
1
def except(*keys)
-
20561
dup.except!(*keys)
-
end
-
-
# Replaces the hash without the given keys.
-
1
def except!(*keys)
-
82076
keys.each { |key| delete(key) }
-
20563
self
-
end
-
end
-
1
require 'active_support/hash_with_indifferent_access'
-
-
1
class Hash
-
-
# Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
-
#
-
# { a: 1 }.with_indifferent_access['a'] # => 1
-
1
def with_indifferent_access
-
4802
ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
-
end
-
-
# Called when object is nested under an object that receives
-
# #with_indifferent_access. This method will be called on the current object
-
# by the enclosing object and is aliased to #with_indifferent_access by
-
# default. Subclasses of Hash may overwrite this method to return +self+ if
-
# converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
-
# desirable.
-
#
-
# b = { b: 1 }
-
# { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
-
1
alias nested_under_indifferent_access with_indifferent_access
-
end
-
1
class Hash
-
# Return a new hash with all keys converted using the block operation.
-
#
-
# hash = { name: 'Rob', age: '28' }
-
#
-
# hash.transform_keys{ |key| key.to_s.upcase }
-
# # => { "NAME" => "Rob", "AGE" => "28" }
-
1
def transform_keys
-
12425
result = {}
-
12425
each_key do |key|
-
11192
result[yield(key)] = self[key]
-
end
-
12425
result
-
end
-
-
# Destructively convert all keys using the block operations.
-
# Same as transform_keys but modifies +self+.
-
1
def transform_keys!
-
1788
keys.each do |key|
-
6162
self[yield(key)] = delete(key)
-
end
-
1788
self
-
end
-
-
# Return a new hash with all keys converted to strings.
-
#
-
# hash = { name: 'Rob', age: '28' }
-
#
-
# hash.stringify_keys
-
# #=> { "name" => "Rob", "age" => "28" }
-
1
def stringify_keys
-
9254
transform_keys{ |key| key.to_s }
-
end
-
-
# Destructively convert all keys to strings. Same as
-
# +stringify_keys+, but modifies +self+.
-
1
def stringify_keys!
-
6958
transform_keys!{ |key| key.to_s }
-
end
-
-
# Return a new hash with all keys converted to symbols, as long as
-
# they respond to +to_sym+.
-
#
-
# hash = { 'name' => 'Rob', 'age' => '28' }
-
#
-
# hash.symbolize_keys
-
# #=> { name: "Rob", age: "28" }
-
1
def symbolize_keys
-
14363
transform_keys{ |key| key.to_sym rescue key }
-
end
-
1
alias_method :to_options, :symbolize_keys
-
-
# Destructively convert all keys to symbols, as long as they respond
-
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
-
1
def symbolize_keys!
-
992
transform_keys!{ |key| key.to_sym rescue key }
-
end
-
1
alias_method :to_options!, :symbolize_keys!
-
-
# Validate all keys in a hash match <tt>*valid_keys</tt>, raising ArgumentError
-
# on a mismatch. Note that keys are NOT treated indifferently, meaning if you
-
# use strings for keys but assert symbols as keys, this will fail.
-
#
-
# { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
-
# { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
-
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
-
1
def assert_valid_keys(*valid_keys)
-
26
valid_keys.flatten!
-
26
each_key do |k|
-
23
raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
-
end
-
end
-
-
# Return a new hash with all keys converted by the block operation.
-
# This includes the keys from the root hash and from all
-
# nested hashes.
-
#
-
# hash = { person: { name: 'Rob', age: '28' } }
-
#
-
# hash.deep_transform_keys{ |key| key.to_s.upcase }
-
# # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } }
-
1
def deep_transform_keys(&block)
-
12496
result = {}
-
12496
each do |key, value|
-
32397
result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
-
end
-
12496
result
-
end
-
-
# Destructively convert all keys by using the block operation.
-
# This includes the keys from the root hash and from all
-
# nested hashes.
-
1
def deep_transform_keys!(&block)
-
keys.each do |key|
-
value = delete(key)
-
self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value
-
end
-
self
-
end
-
-
# Return a new hash with all keys converted to strings.
-
# This includes the keys from the root hash and from all
-
# nested hashes.
-
#
-
# hash = { person: { name: 'Rob', age: '28' } }
-
#
-
# hash.deep_stringify_keys
-
# # => { "person" => { "name" => "Rob", "age" => "28" } }
-
1
def deep_stringify_keys
-
deep_transform_keys{ |key| key.to_s }
-
end
-
-
# Destructively convert all keys to strings.
-
# This includes the keys from the root hash and from all
-
# nested hashes.
-
1
def deep_stringify_keys!
-
deep_transform_keys!{ |key| key.to_s }
-
end
-
-
# Return a new hash with all keys converted to symbols, as long as
-
# they respond to +to_sym+. This includes the keys from the root hash
-
# and from all nested hashes.
-
#
-
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
-
#
-
# hash.deep_symbolize_keys
-
# # => { person: { name: "Rob", age: "28" } }
-
1
def deep_symbolize_keys
-
33918
deep_transform_keys{ |key| key.to_sym rescue key }
-
end
-
-
# Destructively convert all keys to symbols, as long as they respond
-
# to +to_sym+. This includes the keys from the root hash and from all
-
# nested hashes.
-
1
def deep_symbolize_keys!
-
deep_transform_keys!{ |key| key.to_sym rescue key }
-
end
-
end
-
1
class Hash
-
# Merges the caller into +other_hash+. For example,
-
#
-
# options = options.reverse_merge(size: 25, velocity: 10)
-
#
-
# is equivalent to
-
#
-
# options = { size: 25, velocity: 10 }.merge(options)
-
#
-
# This is particularly useful for initializing an options hash
-
# with default values.
-
1
def reverse_merge(other_hash)
-
1059
other_hash.merge(self)
-
end
-
-
# Destructive +reverse_merge+.
-
1
def reverse_merge!(other_hash)
-
# right wins if there is no left
-
15012
merge!( other_hash ){|key,left,right| left }
-
end
-
1
alias_method :reverse_update, :reverse_merge!
-
end
-
1
class Hash
-
# Slice a hash to include only the given keys. This is useful for
-
# limiting an options hash to valid keys before passing to a method:
-
#
-
# def search(criteria = {})
-
# criteria.assert_valid_keys(:mass, :velocity, :time)
-
# end
-
#
-
# search(options.slice(:mass, :velocity, :time))
-
#
-
# If you have an array of keys you want to limit to, you should splat them:
-
#
-
# valid_keys = [:mass, :velocity, :time]
-
# search(options.slice(*valid_keys))
-
1
def slice(*keys)
-
5434
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
-
28316
keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
-
end
-
-
# Replaces the hash with only the given keys.
-
# Returns a hash containing the removed key/value pairs.
-
#
-
# { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
-
# # => {:c=>3, :d=>4}
-
1
def slice!(*keys)
-
663
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
-
663
omit = slice(*self.keys - keys)
-
663
hash = slice(*keys)
-
663
replace(hash)
-
663
omit
-
end
-
-
# Removes and returns the key/value pairs matching the given keys.
-
#
-
# { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
-
# { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
-
1
def extract!(*keys)
-
keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
-
end
-
end
-
1
class Integer
-
# Enables the use of time calculations and declarations, like <tt>45.minutes +
-
# 2.hours + 4.years</tt>.
-
#
-
# These methods use Time#advance for precise date calculations when using
-
# <tt>from_now</tt>, +ago+, etc. as well as adding or subtracting their
-
# results from a Time object.
-
#
-
# # equivalent to Time.now.advance(months: 1)
-
# 1.month.from_now
-
#
-
# # equivalent to Time.now.advance(years: 2)
-
# 2.years.from_now
-
#
-
# # equivalent to Time.now.advance(months: 4, years: 5)
-
# (4.months + 5.years).from_now
-
#
-
# While these methods provide precise calculation when used as in the examples
-
# above, care should be taken to note that this is not true if the result of
-
# +months+, +years+, etc is converted before use:
-
#
-
# # equivalent to 30.days.to_i.from_now
-
# 1.month.to_i.from_now
-
#
-
# # equivalent to 365.25.days.to_f.from_now
-
# 1.year.to_f.from_now
-
#
-
# In such cases, Ruby's core
-
# Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
-
# Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
-
# date and time arithmetic.
-
1
def months
-
70
ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
-
end
-
1
alias :month :months
-
-
1
def years
-
87
ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]])
-
end
-
1
alias :year :years
-
end
-
1
module Kernel
-
# class_eval on an object acts like singleton_class.class_eval.
-
1
def class_eval(*args, &block)
-
singleton_class.class_eval(*args, &block)
-
end
-
end
-
1
class LoadError
-
1
REGEXPS = [
-
/^no such file to load -- (.+)$/i,
-
/^Missing \w+ (?:file\s*)?([^\s]+.rb)$/i,
-
/^Missing API definition file in (.+)$/i,
-
/^cannot load such file -- (.+)$/i,
-
]
-
-
1
unless method_defined?(:path)
-
1
def path
-
@path ||= begin
-
REGEXPS.find do |regex|
-
message =~ regex
-
end
-
$1
-
351
end
-
end
-
end
-
-
1
def is_missing?(location)
-
350
location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
-
end
-
end
-
-
1
MissingSourceFile = LoadError
-
1
require 'active_support/core_ext/module/aliasing'
-
1
require 'active_support/core_ext/module/introspection'
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/core_ext/module/reachable'
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
1
require 'active_support/core_ext/module/attr_internal'
-
1
require 'active_support/core_ext/module/delegation'
-
1
require 'active_support/core_ext/module/deprecation'
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'active_support/core_ext/module/qualified_const'
-
1
class Module
-
# Encapsulates the common pattern of:
-
#
-
# alias_method :foo_without_feature, :foo
-
# alias_method :foo, :foo_with_feature
-
#
-
# With this, you simply do:
-
#
-
# alias_method_chain :foo, :feature
-
#
-
# And both aliases are set up for you.
-
#
-
# Query and bang methods (foo?, foo!) keep the same punctuation:
-
#
-
# alias_method_chain :foo?, :feature
-
#
-
# is equivalent to
-
#
-
# alias_method :foo_without_feature?, :foo?
-
# alias_method :foo?, :foo_with_feature?
-
#
-
# so you can safely chain foo, foo?, and foo! with the same feature.
-
1
def alias_method_chain(target, feature)
-
# Strip out punctuation on predicates or bang methods since
-
# e.g. target?_without_feature is not a valid method name.
-
8
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
-
8
yield(aliased_target, punctuation) if block_given?
-
-
8
with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
-
8
without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
-
-
8
alias_method without_method, target
-
8
alias_method target, with_method
-
-
case
-
when public_method_defined?(without_method)
-
7
public target
-
when protected_method_defined?(without_method)
-
protected target
-
when private_method_defined?(without_method)
-
1
private target
-
8
end
-
end
-
-
# Allows you to make aliases for attributes, which includes
-
# getter, setter, and query methods.
-
#
-
# class Content < ActiveRecord::Base
-
# # has a title attribute
-
# end
-
#
-
# class Email < Content
-
# alias_attribute :subject, :title
-
# end
-
#
-
# e = Email.find(1)
-
# e.title # => "Superstars"
-
# e.subject # => "Superstars"
-
# e.subject? # => true
-
# e.subject = "Megastars"
-
# e.title # => "Megastars"
-
1
def alias_attribute(new_name, old_name)
-
module_eval <<-STR, __FILE__, __LINE__ + 1
-
def #{new_name}; self.#{old_name}; end # def subject; self.title; end
-
def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
-
def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
-
STR
-
end
-
end
-
1
class Module
-
# A module may or may not have a name.
-
#
-
# module M; end
-
# M.name # => "M"
-
#
-
# m = Module.new
-
# m.name # => nil
-
#
-
# A module gets a name when it is first assigned to a constant. Either
-
# via the +module+ or +class+ keyword or by an explicit assignment:
-
#
-
# m = Module.new # creates an anonymous module
-
# M = m # => m gets a name here as a side-effect
-
# m.name # => "M"
-
1
def anonymous?
-
4505
name.nil?
-
end
-
end
-
1
class Module
-
# Declares an attribute reader backed by an internally-named instance variable.
-
1
def attr_internal_reader(*attrs)
-
22
attrs.each {|attr_name| attr_internal_define(attr_name, :reader)}
-
end
-
-
# Declares an attribute writer backed by an internally-named instance variable.
-
1
def attr_internal_writer(*attrs)
-
28
attrs.each {|attr_name| attr_internal_define(attr_name, :writer)}
-
end
-
-
# Declares an attribute reader and writer backed by an internally-named instance
-
# variable.
-
1
def attr_internal_accessor(*attrs)
-
9
attr_internal_reader(*attrs)
-
9
attr_internal_writer(*attrs)
-
end
-
1
alias_method :attr_internal, :attr_internal_accessor
-
-
2
class << self; attr_accessor :attr_internal_naming_format end
-
1
self.attr_internal_naming_format = '@_%s'
-
-
1
private
-
1
def attr_internal_ivar_name(attr)
-
29
Module.attr_internal_naming_format % attr
-
end
-
-
1
def attr_internal_define(attr_name, type)
-
29
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
-
29
class_eval do # class_eval is necessary on 1.9 or else the methods a made private
-
# use native attr_* methods as they are faster on some Ruby implementations
-
29
send("attr_#{type}", internal_name)
-
end
-
29
attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
-
29
alias_method attr_name, internal_name
-
29
remove_method internal_name
-
end
-
end
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
class Module
-
1
def mattr_reader(*syms)
-
28
options = syms.extract_options!
-
28
syms.each do |sym|
-
28
raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
-
28
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
@@#{sym} = nil unless defined? @@#{sym}
-
-
def self.#{sym}
-
@@#{sym}
-
end
-
EOS
-
-
28
unless options[:instance_reader] == false || options[:instance_accessor] == false
-
27
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def #{sym}
-
@@#{sym}
-
end
-
EOS
-
end
-
end
-
end
-
-
1
def mattr_writer(*syms)
-
29
options = syms.extract_options!
-
29
syms.each do |sym|
-
29
raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
-
29
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def self.#{sym}=(obj)
-
@@#{sym} = obj
-
end
-
EOS
-
-
29
unless options[:instance_writer] == false || options[:instance_accessor] == false
-
21
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def #{sym}=(obj)
-
@@#{sym} = obj
-
end
-
EOS
-
end
-
end
-
end
-
-
# Extends the module object with module and instance accessors for class attributes,
-
# just like the native attr* accessors for instance attributes.
-
#
-
# module AppConfiguration
-
# mattr_accessor :google_api_key
-
#
-
# self.google_api_key = "123456789"
-
# end
-
#
-
# AppConfiguration.google_api_key # => "123456789"
-
# AppConfiguration.google_api_key = "overriding the api key!"
-
# AppConfiguration.google_api_key # => "overriding the api key!"
-
#
-
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
-
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
-
# To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
-
1
def mattr_accessor(*syms)
-
28
mattr_reader(*syms)
-
28
mattr_writer(*syms)
-
end
-
end
-
1
class Module
-
# Provides a delegate class method to easily expose contained objects' public methods
-
# as your own. Pass one or more methods (specified as symbols or strings)
-
# and the name of the target object via the <tt>:to</tt> option (also a symbol
-
# or string). At least one method and the <tt>:to</tt> option are required.
-
#
-
# Delegation is particularly useful with Active Record associations:
-
#
-
# class Greeter < ActiveRecord::Base
-
# def hello
-
# 'hello'
-
# end
-
#
-
# def goodbye
-
# 'goodbye'
-
# end
-
# end
-
#
-
# class Foo < ActiveRecord::Base
-
# belongs_to :greeter
-
# delegate :hello, to: :greeter
-
# end
-
#
-
# Foo.new.hello # => "hello"
-
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
-
#
-
# Multiple delegates to the same target are allowed:
-
#
-
# class Foo < ActiveRecord::Base
-
# belongs_to :greeter
-
# delegate :hello, :goodbye, to: :greeter
-
# end
-
#
-
# Foo.new.goodbye # => "goodbye"
-
#
-
# Methods can be delegated to instance variables, class variables, or constants
-
# by providing them as a symbols:
-
#
-
# class Foo
-
# CONSTANT_ARRAY = [0,1,2,3]
-
# @@class_array = [4,5,6,7]
-
#
-
# def initialize
-
# @instance_array = [8,9,10,11]
-
# end
-
# delegate :sum, to: :CONSTANT_ARRAY
-
# delegate :min, to: :@@class_array
-
# delegate :max, to: :@instance_array
-
# end
-
#
-
# Foo.new.sum # => 6
-
# Foo.new.min # => 4
-
# Foo.new.max # => 11
-
#
-
# It's also possible to delegate a method to the class by using +:class+:
-
#
-
# class Foo
-
# def self.hello
-
# "world"
-
# end
-
#
-
# delegate :hello, to: :class
-
# end
-
#
-
# Foo.new.hello # => "world"
-
#
-
# Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
-
# is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
-
# delegated to.
-
#
-
# Person = Struct.new(:name, :address)
-
#
-
# class Invoice < Struct.new(:client)
-
# delegate :name, :address, to: :client, prefix: true
-
# end
-
#
-
# john_doe = Person.new('John Doe', 'Vimmersvej 13')
-
# invoice = Invoice.new(john_doe)
-
# invoice.client_name # => "John Doe"
-
# invoice.client_address # => "Vimmersvej 13"
-
#
-
# It is also possible to supply a custom prefix.
-
#
-
# class Invoice < Struct.new(:client)
-
# delegate :name, :address, to: :client, prefix: :customer
-
# end
-
#
-
# invoice = Invoice.new(john_doe)
-
# invoice.customer_name # => 'John Doe'
-
# invoice.customer_address # => 'Vimmersvej 13'
-
#
-
# If the delegate object is +nil+ an exception is raised, and that happens
-
# no matter whether +nil+ responds to the delegated method. You can get a
-
# +nil+ instead with the +:allow_nil+ option.
-
#
-
# class Foo
-
# attr_accessor :bar
-
# def initialize(bar = nil)
-
# @bar = bar
-
# end
-
# delegate :zoo, to: :bar
-
# end
-
#
-
# Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
-
#
-
# class Foo
-
# attr_accessor :bar
-
# def initialize(bar = nil)
-
# @bar = bar
-
# end
-
# delegate :zoo, to: :bar, allow_nil: true
-
# end
-
#
-
# Foo.new.zoo # returns nil
-
1
def delegate(*methods)
-
371
options = methods.pop
-
371
unless options.is_a?(Hash) && to = options[:to]
-
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
-
end
-
-
371
prefix, allow_nil = options.values_at(:prefix, :allow_nil)
-
-
371
if prefix == true && to =~ /^[^a-z_]/
-
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
-
end
-
-
371
method_prefix = \
-
if prefix
-
"#{prefix == true ? to : prefix}_"
-
else
-
371
''
-
end
-
-
371
file, line = caller.first.split(':', 2)
-
371
line = line.to_i
-
-
371
to = to.to_s
-
371
to = 'self.class' if to == 'class'
-
-
371
methods.each do |method|
-
# Attribute writer methods only accept one argument. Makes sure []=
-
# methods still accept two arguments.
-
811
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
-
-
811
if allow_nil
-
6
module_eval(<<-EOS, file, line - 2)
-
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
-
if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
-
#{to}.#{method}(#{definition}) # client.name(*args, &block)
-
end # end
-
end # end
-
EOS
-
else
-
805
exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
-
-
805
module_eval(<<-EOS, file, line - 1)
-
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
-
#{to}.#{method}(#{definition}) # client.name(*args, &block)
-
rescue NoMethodError # rescue NoMethodError
-
if #{to}.nil? # if client.nil?
-
#{exception} # # add helpful message to the exception
-
else # else
-
raise # raise
-
end # end
-
end # end
-
EOS
-
end
-
end
-
end
-
end
-
1
require 'active_support/deprecation/method_wrappers'
-
-
1
class Module
-
# deprecate :foo
-
# deprecate bar: 'message'
-
# deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
-
#
-
# You can also use custom deprecator instance:
-
#
-
# deprecate :foo, deprecator: MyLib::Deprecator.new
-
# deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
-
#
-
# \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt>
-
# method where you can implement your custom warning behavior.
-
#
-
# class MyLib::Deprecator
-
# def deprecation_warning(deprecated_method_name, message, caller_backtrace)
-
# message = "#{method_name} is deprecated and will be removed from MyLibrary | #{message}"
-
# Kernel.warn message
-
# end
-
# end
-
1
def deprecate(*method_names)
-
2
ActiveSupport::Deprecation.deprecate_methods(self, *method_names)
-
end
-
end
-
1
require 'active_support/inflector'
-
-
1
class Module
-
# Returns the name of the module containing this one.
-
#
-
# M::N.parent_name # => "M"
-
1
def parent_name
-
773
if defined? @parent_name
-
753
@parent_name
-
else
-
20
@parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
-
end
-
end
-
-
# Returns the module which contains this one according to its name.
-
#
-
# module M
-
# module N
-
# end
-
# end
-
# X = M::N
-
#
-
# M::N.parent # => M
-
# X.parent # => M
-
#
-
# The parent of top-level and anonymous modules is Object.
-
#
-
# M.parent # => Object
-
# Module.new.parent # => Object
-
1
def parent
-
591
parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object
-
end
-
-
# Returns all the parents of this module according to its name, ordered from
-
# nested outwards. The receiver is not contained within the result.
-
#
-
# module M
-
# module N
-
# end
-
# end
-
# X = M::N
-
#
-
# M.parents # => [Object]
-
# M::N.parents # => [M, Object]
-
# X.parents # => [M, Object]
-
1
def parents
-
79
parents = []
-
79
if parent_name
-
53
parts = parent_name.split('::')
-
53
until parts.empty?
-
57
parents << ActiveSupport::Inflector.constantize(parts * '::')
-
57
parts.pop
-
end
-
end
-
79
parents << Object unless parents.include? Object
-
79
parents
-
end
-
-
1
def local_constants #:nodoc:
-
constants(false)
-
end
-
-
# *DEPRECATED*: Use +local_constants+ instead.
-
#
-
# Returns the names of the constants defined locally as strings.
-
#
-
# module M
-
# X = 1
-
# end
-
# M.local_constant_names # => ["X"]
-
#
-
# This method is useful for forward compatibility, since Ruby 1.8 returns
-
# constant names as strings, whereas 1.9 returns them as symbols.
-
1
def local_constant_names
-
ActiveSupport::Deprecation.warn 'Module#local_constant_names is deprecated, use Module#local_constants instead'
-
local_constants.map { |c| c.to_s }
-
end
-
end
-
1
require 'active_support/core_ext/string/inflections'
-
-
#--
-
# Allows code reuse in the methods below without polluting Module.
-
#++
-
1
module QualifiedConstUtils
-
1
def self.raise_if_absolute(path)
-
591
raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
-
end
-
-
1
def self.names(path)
-
591
path.split('::')
-
end
-
end
-
-
##
-
# Extends the API for constants to be able to deal with qualified names. Arguments
-
# are assumed to be relative to the receiver.
-
#
-
#--
-
# Qualified names are required to be relative because we are extending existing
-
# methods that expect constant names, ie, relative paths of length 1. For example,
-
# Object.const_get('::String') raises NameError and so does qualified_const_get.
-
#++
-
1
class Module
-
1
def qualified_const_defined?(path, search_parents=true)
-
591
QualifiedConstUtils.raise_if_absolute(path)
-
-
591
QualifiedConstUtils.names(path).inject(self) do |mod, name|
-
645
return unless mod.const_defined?(name, search_parents)
-
645
mod.const_get(name)
-
end
-
591
return true
-
end
-
-
1
def qualified_const_get(path)
-
QualifiedConstUtils.raise_if_absolute(path)
-
-
QualifiedConstUtils.names(path).inject(self) do |mod, name|
-
mod.const_get(name)
-
end
-
end
-
-
1
def qualified_const_set(path, value)
-
QualifiedConstUtils.raise_if_absolute(path)
-
-
const_name = path.demodulize
-
mod_name = path.deconstantize
-
mod = mod_name.empty? ? self : qualified_const_get(mod_name)
-
mod.const_set(const_name, value)
-
end
-
end
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
class Module
-
1
def reachable? #:nodoc:
-
!anonymous? && name.safe_constantize.equal?(self)
-
end
-
end
-
1
class Module
-
1
def remove_possible_method(method)
-
11508
if method_defined?(method) || private_method_defined?(method)
-
4267
undef_method(method)
-
end
-
end
-
-
1
def redefine_method(method, &block)
-
703
remove_possible_method(method)
-
703
define_method(method, &block)
-
end
-
end
-
1
class NameError
-
# Extract the name of the missing constant from the exception message.
-
1
def missing_name
-
22
if /undefined local variable or method/ !~ message
-
22
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
-
end
-
end
-
-
# Was this exception raised because the given name was missing?
-
1
def missing_name?(name)
-
22
if name.is_a? Symbol
-
last_name = (missing_name || '').split('::').last
-
last_name == name.to_s
-
else
-
22
missing_name == name.to_s
-
end
-
end
-
end
-
1
class Numeric
-
1
KILOBYTE = 1024
-
1
MEGABYTE = KILOBYTE * 1024
-
1
GIGABYTE = MEGABYTE * 1024
-
1
TERABYTE = GIGABYTE * 1024
-
1
PETABYTE = TERABYTE * 1024
-
1
EXABYTE = PETABYTE * 1024
-
-
# Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
-
1
def bytes
-
self
-
end
-
1
alias :byte :bytes
-
-
1
def kilobytes
-
1
self * KILOBYTE
-
end
-
1
alias :kilobyte :kilobytes
-
-
1
def megabytes
-
26
self * MEGABYTE
-
end
-
1
alias :megabyte :megabytes
-
-
1
def gigabytes
-
self * GIGABYTE
-
end
-
1
alias :gigabyte :gigabytes
-
-
1
def terabytes
-
self * TERABYTE
-
end
-
1
alias :terabyte :terabytes
-
-
1
def petabytes
-
self * PETABYTE
-
end
-
1
alias :petabyte :petabytes
-
-
1
def exabytes
-
self * EXABYTE
-
end
-
1
alias :exabyte :exabytes
-
end
-
1
require 'active_support/duration'
-
1
require 'active_support/core_ext/time/calculations'
-
1
require 'active_support/core_ext/time/acts_like'
-
-
1
class Numeric
-
# Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
-
#
-
# These methods use Time#advance for precise date calculations when using from_now, ago, etc.
-
# as well as adding or subtracting their results from a Time object. For example:
-
#
-
# # equivalent to Time.current.advance(months: 1)
-
# 1.month.from_now
-
#
-
# # equivalent to Time.current.advance(years: 2)
-
# 2.years.from_now
-
#
-
# # equivalent to Time.current.advance(months: 4, years: 5)
-
# (4.months + 5.years).from_now
-
#
-
# While these methods provide precise calculation when used as in the examples above, care
-
# should be taken to note that this is not true if the result of `months', `years', etc is
-
# converted before use:
-
#
-
# # equivalent to 30.days.to_i.from_now
-
# 1.month.to_i.from_now
-
#
-
# # equivalent to 365.25.days.to_f.from_now
-
# 1.year.to_f.from_now
-
#
-
# In such cases, Ruby's core
-
# Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
-
# Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
-
# date and time arithmetic.
-
1
def seconds
-
212
ActiveSupport::Duration.new(self, [[:seconds, self]])
-
end
-
1
alias :second :seconds
-
-
1
def minutes
-
76
ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]])
-
end
-
1
alias :minute :minutes
-
-
1
def hours
-
308
ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]])
-
end
-
1
alias :hour :hours
-
-
1
def days
-
254
ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
-
end
-
1
alias :day :days
-
-
1
def weeks
-
ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
-
end
-
1
alias :week :weeks
-
-
1
def fortnights
-
ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]])
-
end
-
1
alias :fortnight :fortnights
-
-
# Reads best without arguments: 10.minutes.ago
-
1
def ago(time = ::Time.current)
-
time - self
-
end
-
-
# Reads best with argument: 10.minutes.until(time)
-
1
alias :until :ago
-
-
# Reads best with argument: 10.minutes.since(time)
-
1
def since(time = ::Time.current)
-
time + self
-
end
-
-
# Reads best without arguments: 10.minutes.from_now
-
1
alias :from_now :since
-
end
-
1
require 'active_support/core_ext/object/acts_like'
-
1
require 'active_support/core_ext/object/blank'
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'active_support/core_ext/object/deep_dup'
-
1
require 'active_support/core_ext/object/try'
-
1
require 'active_support/core_ext/object/inclusion'
-
-
1
require 'active_support/core_ext/object/conversions'
-
1
require 'active_support/core_ext/object/instance_variables'
-
-
1
require 'active_support/core_ext/object/to_json'
-
1
require 'active_support/core_ext/object/to_param'
-
1
require 'active_support/core_ext/object/to_query'
-
1
require 'active_support/core_ext/object/with_options'
-
1
class Object
-
# A duck-type assistant method. For example, Active Support extends Date
-
# to define an acts_like_date? method, and extends Time to define
-
# acts_like_time?. As a result, we can do "x.acts_like?(:time)" and
-
# "x.acts_like?(:date)" to do duck-type-safe comparisons, since classes that
-
# we want to act like Time simply need to define an acts_like_time? method.
-
1
def acts_like?(duck)
-
1175
respond_to? :"acts_like_#{duck}?"
-
end
-
end
-
# encoding: utf-8
-
-
1
class Object
-
# An object is blank if it's false, empty, or a whitespace string.
-
# For example, '', ' ', +nil+, [], and {} are all blank.
-
#
-
# This simplifies:
-
#
-
# if address.nil? || address.empty?
-
#
-
# ...to:
-
#
-
# if address.blank?
-
1
def blank?
-
respond_to?(:empty?) ? empty? : !self
-
end
-
-
# An object is present if it's not <tt>blank?</tt>.
-
1
def present?
-
62725
!blank?
-
end
-
-
# Returns object if it's <tt>present?</tt> otherwise returns +nil+.
-
# <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
-
#
-
# This is handy for any representation of objects where blank is the same
-
# as not present at all. For example, this simplifies a common check for
-
# HTTP POST/query parameters:
-
#
-
# state = params[:state] if params[:state].present?
-
# country = params[:country] if params[:country].present?
-
# region = state || country || 'US'
-
#
-
# ...becomes:
-
#
-
# region = params[:state].presence || params[:country].presence || 'US'
-
1
def presence
-
22349
self if present?
-
end
-
end
-
-
1
class NilClass
-
# +nil+ is blank:
-
#
-
# nil.blank? # => true
-
1
def blank?
-
true
-
end
-
end
-
-
1
class FalseClass
-
# +false+ is blank:
-
#
-
# false.blank? # => true
-
1
def blank?
-
1
true
-
end
-
end
-
-
1
class TrueClass
-
# +true+ is not blank:
-
#
-
# true.blank? # => false
-
1
def blank?
-
263
false
-
end
-
end
-
-
1
class Array
-
# An array is blank if it's empty:
-
#
-
# [].blank? # => true
-
# [1,2,3].blank? # => false
-
1
alias_method :blank?, :empty?
-
end
-
-
1
class Hash
-
# A hash is blank if it's empty:
-
#
-
# {}.blank? # => true
-
# { key: 'value' }.blank? # => false
-
1
alias_method :blank?, :empty?
-
end
-
-
1
class String
-
# A string is blank if it's empty or contains whitespaces only:
-
#
-
# ''.blank? # => true
-
# ' '.blank? # => true
-
# ' '.blank? # => true
-
# ' something here '.blank? # => false
-
1
def blank?
-
71902
self !~ /[^[:space:]]/
-
end
-
end
-
-
1
class Numeric #:nodoc:
-
# No number is blank:
-
#
-
# 1.blank? # => false
-
# 0.blank? # => false
-
1
def blank?
-
86
false
-
end
-
end
-
1
require 'active_support/core_ext/object/to_param'
-
1
require 'active_support/core_ext/object/to_query'
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/hash/conversions'
-
1
require 'active_support/core_ext/object/duplicable'
-
-
1
class Object
-
# Returns a deep copy of object if it's duplicable. If it's
-
# not duplicable, returns +self+.
-
#
-
# object = Object.new
-
# dup = object.deep_dup
-
# dup.instance_variable_set(:@a, 1)
-
#
-
# object.instance_variable_defined?(:@a) #=> false
-
# dup.instance_variable_defined?(:@a) #=> true
-
1
def deep_dup
-
duplicable? ? dup : self
-
end
-
end
-
-
1
class Array
-
# Returns a deep copy of array.
-
#
-
# array = [1, [2, 3]]
-
# dup = array.deep_dup
-
# dup[1][2] = 4
-
#
-
# array[1][2] #=> nil
-
# dup[1][2] #=> 4
-
1
def deep_dup
-
map { |it| it.deep_dup }
-
end
-
end
-
-
1
class Hash
-
# Returns a deep copy of hash.
-
#
-
# hash = { a: { b: 'b' } }
-
# dup = hash.deep_dup
-
# dup[:a][:c] = 'c'
-
#
-
# hash[:a][:c] #=> nil
-
# dup[:a][:c] #=> "c"
-
1
def deep_dup
-
each_with_object(dup) do |(key, value), hash|
-
hash[key.deep_dup] = value.deep_dup
-
end
-
end
-
end
-
#--
-
# Most objects are cloneable, but not all. For example you can't dup +nil+:
-
#
-
# nil.dup # => TypeError: can't dup NilClass
-
#
-
# Classes may signal their instances are not duplicable removing +dup+/+clone+
-
# or raising exceptions from them. So, to dup an arbitrary object you normally
-
# use an optimistic approach and are ready to catch an exception, say:
-
#
-
# arbitrary_object.dup rescue object
-
#
-
# Rails dups objects in a few critical spots where they are not that arbitrary.
-
# That rescue is very expensive (like 40 times slower than a predicate), and it
-
# is often triggered.
-
#
-
# That's why we hardcode the following cases and check duplicable? instead of
-
# using that rescue idiom.
-
#++
-
1
class Object
-
# Can you safely dup this object?
-
#
-
# False for +nil+, +false+, +true+, symbol, and number objects;
-
# true otherwise.
-
1
def duplicable?
-
13
true
-
end
-
end
-
-
1
class NilClass
-
# +nil+ is not duplicable:
-
#
-
# nil.duplicable? # => false
-
# nil.dup # => TypeError: can't dup NilClass
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class FalseClass
-
# +false+ is not duplicable:
-
#
-
# false.duplicable? # => false
-
# false.dup # => TypeError: can't dup FalseClass
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class TrueClass
-
# +true+ is not duplicable:
-
#
-
# true.duplicable? # => false
-
# true.dup # => TypeError: can't dup TrueClass
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class Symbol
-
# Symbols are not duplicable:
-
#
-
# :my_symbol.duplicable? # => false
-
# :my_symbol.dup # => TypeError: can't dup Symbol
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class Numeric
-
# Numbers are not duplicable:
-
#
-
# 3.duplicable? # => false
-
# 3.dup # => TypeError: can't dup Fixnum
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
require 'bigdecimal'
-
1
class BigDecimal
-
1
begin
-
1
BigDecimal.new('4.56').dup
-
-
def duplicable?
-
true
-
end
-
rescue TypeError
-
# can't dup, so use superclass implementation
-
end
-
end
-
1
class Object
-
# Returns true if this object is included in the argument(s). Argument must be
-
# any object which responds to +#include?+ or optionally, multiple arguments can be passed in. Usage:
-
#
-
# characters = ['Konata', 'Kagami', 'Tsukasa']
-
# 'Konata'.in?(characters) # => true
-
#
-
# character = 'Konata'
-
# character.in?('Konata', 'Kagami', 'Tsukasa') # => true
-
#
-
# This will throw an ArgumentError if a single argument is passed in and it doesn't respond
-
# to +#include?+.
-
1
def in?(*args)
-
if args.length > 1
-
args.include? self
-
else
-
another_object = args.first
-
if another_object.respond_to? :include?
-
another_object.include? self
-
else
-
raise ArgumentError.new 'The single parameter passed to #in? must respond to #include?'
-
end
-
end
-
end
-
end
-
1
class Object
-
# Returns a hash with string keys that maps instance variable names without "@" to their
-
# corresponding values.
-
#
-
# class C
-
# def initialize(x, y)
-
# @x, @y = x, y
-
# end
-
# end
-
#
-
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
-
1
def instance_values
-
Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
-
end
-
-
# Returns an array of instance variable names including "@".
-
#
-
# class C
-
# def initialize(x, y)
-
# @x, @y = x, y
-
# end
-
# end
-
#
-
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
-
1
def instance_variable_names
-
instance_variables.map { |var| var.to_s }
-
end
-
end
-
# Hack to load json gem first so we can overwrite its to_json.
-
1
begin
-
1
require 'json'
-
rescue LoadError
-
end
-
-
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
-
# their default behavior. That said, we need to define the basic to_json method in all of them,
-
# otherwise they will always use to_json gem implementation, which is backwards incompatible in
-
# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
-
# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
-
1
[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
-
9
klass.class_eval do
-
# Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
-
9
def to_json(options = nil)
-
20
ActiveSupport::JSON.encode(self, options)
-
end
-
end
-
end
-
-
1
module Process
-
1
class Status
-
1
def as_json(options = nil)
-
{ :exitstatus => exitstatus, :pid => pid }
-
end
-
end
-
end
-
1
class Object
-
# Alias of <tt>to_s</tt>.
-
1
def to_param
-
7592
to_s
-
end
-
end
-
-
1
class NilClass
-
# Returns +self+.
-
1
def to_param
-
1944
self
-
end
-
end
-
-
1
class TrueClass
-
# Returns +self+.
-
1
def to_param
-
4
self
-
end
-
end
-
-
1
class FalseClass
-
# Returns +self+.
-
1
def to_param
-
2
self
-
end
-
end
-
-
1
class Array
-
# Calls <tt>to_param</tt> on all its elements and joins the result with
-
# slashes. This is used by <tt>url_for</tt> in Action Pack.
-
1
def to_param
-
111
collect { |e| e.to_param }.join '/'
-
end
-
end
-
-
1
class Hash
-
# Returns a string representation of the receiver suitable for use as a URL
-
# query string:
-
#
-
# {name: 'David', nationality: 'Danish'}.to_param
-
# # => "name=David&nationality=Danish"
-
#
-
# An optional namespace can be passed to enclose the param names:
-
#
-
# {name: 'David', nationality: 'Danish'}.to_param('user')
-
# # => "user[name]=David&user[nationality]=Danish"
-
#
-
# The string pairs "key=value" that conform the query string
-
# are sorted lexicographically in ascending order.
-
#
-
# This method is also aliased as +to_query+.
-
1
def to_param(namespace = nil)
-
collect do |key, value|
-
342
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
-
1391
end.sort * '&'
-
end
-
end
-
1
require 'active_support/core_ext/object/to_param'
-
-
1
class Object
-
# Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the
-
# param name.
-
#
-
# Note: This method is defined as a default implementation for all Objects for Hash#to_query to work.
-
1
def to_query(key)
-
312
require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
-
312
"#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
-
end
-
end
-
-
1
class Array
-
# Converts an array into a string suitable for use as a URL query string,
-
# using the given +key+ as the param name.
-
#
-
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
-
1
def to_query(key)
-
6
prefix = "#{key}[]"
-
17
collect { |value| value.to_query(prefix) }.join '&'
-
end
-
end
-
-
1
class Hash
-
1
alias_method :to_query, :to_param
-
end
-
1
class Object
-
# Invokes the public method identified by the symbol +method+, passing it any arguments
-
# and/or the block specified, just like the regular Ruby <tt>Object#public_send</tt> does.
-
#
-
# *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
-
# and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
-
#
-
# This is also true if the receiving object does not implemented the tried method. It will
-
# return +nil+ in that case as well.
-
#
-
# If try is called without a method to call, it will yield any given block with the object.
-
#
-
# Please also note that +try+ is defined on +Object+, therefore it won't work with
-
# subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will
-
# delegate +try+ to target instead of calling it on delegator itself.
-
#
-
# Without +try+
-
# @person && @person.name
-
# or
-
# @person ? @person.name : nil
-
#
-
# With +try+
-
# @person.try(:name)
-
#
-
# +try+ also accepts arguments and/or a block, for the method it is trying
-
# Person.try(:find, 1)
-
# @people.try(:collect) {|p| p.name}
-
#
-
# Without a method argument try will yield to the block unless the receiver is nil.
-
# @person.try { |p| "#{p.first_name} #{p.last_name}" }
-
#
-
# +try+ behaves like +Object#public_send+, unless called on +NilClass+.
-
1
def try(*a, &b)
-
3197
if a.empty? && block_given?
-
yield self
-
else
-
3197
public_send(*a, &b) if respond_to?(a.first)
-
end
-
end
-
-
# Same as #try, but will raise a NoMethodError exception if the receiving is not nil and
-
# does not implemented the tried method.
-
1
def try!(*a, &b)
-
if a.empty? && block_given?
-
yield self
-
else
-
public_send(*a, &b)
-
end
-
end
-
end
-
-
1
class NilClass
-
# Calling +try+ on +nil+ always returns +nil+.
-
# It becomes specially helpful when navigating through associations that may return +nil+.
-
#
-
# nil.try(:name) # => nil
-
#
-
# Without +try+
-
# @person && !@person.children.blank? && @person.children.first.name
-
#
-
# With +try+
-
# @person.try(:children).try(:first).try(:name)
-
1
def try(*args)
-
nil
-
end
-
-
1
def try!(*args)
-
nil
-
end
-
end
-
1
require 'active_support/option_merger'
-
-
1
class Object
-
# An elegant way to factor duplication out of options passed to a series of
-
# method calls. Each method called in the block, with the block variable as
-
# the receiver, will have its options merged with the default +options+ hash
-
# provided. Each method called on the block variable must take an options
-
# hash as its final argument.
-
#
-
# Without <tt>with_options></tt>, this code contains duplication:
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :customers, dependent: :destroy
-
# has_many :products, dependent: :destroy
-
# has_many :invoices, dependent: :destroy
-
# has_many :expenses, dependent: :destroy
-
# end
-
#
-
# Using <tt>with_options</tt>, we can remove the duplication:
-
#
-
# class Account < ActiveRecord::Base
-
# with_options dependent: :destroy do |assoc|
-
# assoc.has_many :customers
-
# assoc.has_many :products
-
# assoc.has_many :invoices
-
# assoc.has_many :expenses
-
# end
-
# end
-
#
-
# It can also be used with an explicit receiver:
-
#
-
# I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n|
-
# subject i18n.t :subject
-
# body i18n.t :body, user_name: user.name
-
# end
-
#
-
# <tt>with_options</tt> can also be nested since the call is forwarded to its receiver.
-
# Each nesting level will merge inherited defaults in addition to their own.
-
1
def with_options(options)
-
428
yield ActiveSupport::OptionMerger.new(self, options)
-
end
-
end
-
1
require "active_support/core_ext/kernel/singleton_class"
-
1
require "active_support/deprecation"
-
-
1
class Proc #:nodoc:
-
1
def bind(object)
-
ActiveSupport::Deprecation.warn 'Proc#bind is deprecated and will be removed in future versions'
-
-
block, time = self, Time.now
-
object.class_eval do
-
method_name = "__bind_#{time.to_i}_#{time.usec}"
-
define_method(method_name, &block)
-
method = instance_method(method_name)
-
remove_method(method_name)
-
method
-
end.bind(object)
-
end
-
end
-
1
require 'active_support/core_ext/range/conversions'
-
1
require 'active_support/core_ext/range/include_range'
-
1
require 'active_support/core_ext/range/overlaps'
-
1
class Range
-
1
RANGE_FORMATS = {
-
:db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
-
}
-
-
# Gives a human readable format of the range.
-
#
-
# (1..100).to_formatted_s # => "1..100"
-
1
def to_formatted_s(format = :default)
-
if formatter = RANGE_FORMATS[format]
-
formatter.call(first, last)
-
else
-
to_default_s
-
end
-
end
-
-
1
alias_method :to_default_s, :to_s
-
1
alias_method :to_s, :to_formatted_s
-
end
-
1
class Range
-
# Extends the default Range#include? to support range comparisons.
-
# (1..5).include?(1..5) # => true
-
# (1..5).include?(2..3) # => true
-
# (1..5).include?(2..6) # => false
-
#
-
# The native Range#include? behavior is untouched.
-
# ('a'..'f').include?('c') # => true
-
# (5..9).include?(11) # => false
-
1
def include_with_range?(value)
-
4774
if value.is_a?(::Range)
-
# 1...10 includes 1..9 but it does not include 1..10.
-
operator = exclude_end? && !value.exclude_end? ? :< : :<=
-
include_without_range?(value.first) && value.last.send(operator, last)
-
else
-
4774
include_without_range?(value)
-
end
-
end
-
-
1
alias_method_chain :include?, :range
-
end
-
1
class Range
-
# Compare two ranges and see if they overlap each other
-
# (1..5).overlaps?(4..6) # => true
-
# (1..5).overlaps?(7..9) # => false
-
1
def overlaps?(other)
-
cover?(other.first) || other.cover?(first)
-
end
-
end
-
1
class Regexp #:nodoc:
-
1
def multiline?
-
1362
options & MULTILINE == MULTILINE
-
end
-
end
-
1
class String
-
# If you pass a single Fixnum, returns a substring of one character at that
-
# position. The first character of the string is at position 0, the next at
-
# position 1, and so on. If a range is supplied, a substring containing
-
# characters at offsets given by the range is returned. In both cases, if an
-
# offset is negative, it is counted from the end of the string. Returns nil
-
# if the initial offset falls outside the string. Returns an empty string if
-
# the beginning of the range is greater than the end of the string.
-
#
-
# str = "hello"
-
# str.at(0) #=> "h"
-
# str.at(1..3) #=> "ell"
-
# str.at(-2) #=> "l"
-
# str.at(-2..-1) #=> "lo"
-
# str.at(5) #=> nil
-
# str.at(5..-1) #=> ""
-
#
-
# If a Regexp is given, the matching portion of the string is returned.
-
# If a String is given, that given string is returned if it occurs in
-
# the string. In both cases, nil is returned if there is no match.
-
#
-
# str = "hello"
-
# str.at(/lo/) #=> "lo"
-
# str.at(/ol/) #=> nil
-
# str.at("lo") #=> "lo"
-
# str.at("ol") #=> nil
-
1
def at(position)
-
self[position]
-
end
-
-
# Returns a substring from the given position to the end of the string.
-
# If the position is negative, it is counted from the end of the string.
-
#
-
# str = "hello"
-
# str.from(0) #=> "hello"
-
# str.from(3) #=> "lo"
-
# str.from(-2) #=> "lo"
-
#
-
# You can mix it with +to+ method and do fun things like:
-
#
-
# str = "hello"
-
# str.from(0).to(-1) #=> "hello"
-
# str.from(1).to(-2) #=> "ell"
-
1
def from(position)
-
25
self[position..-1]
-
end
-
-
# Returns a substring from the beginning of the string to the given position.
-
# If the position is negative, it is counted from the end of the string.
-
#
-
# str = "hello"
-
# str.to(0) #=> "h"
-
# str.to(3) #=> "hell"
-
# str.to(-2) #=> "hell"
-
#
-
# You can mix it with +from+ method and do fun things like:
-
#
-
# str = "hello"
-
# str.from(0).to(-1) #=> "hello"
-
# str.from(1).to(-2) #=> "ell"
-
1
def to(position)
-
2375
self[0..position]
-
end
-
-
# Returns the first character. If a limit is supplied, returns a substring
-
# from the beginning of the string until it reaches the limit value. If the
-
# given limit is greater than or equal to the string length, returns self.
-
#
-
# str = "hello"
-
# str.first #=> "h"
-
# str.first(1) #=> "h"
-
# str.first(2) #=> "he"
-
# str.first(0) #=> ""
-
# str.first(6) #=> "hello"
-
1
def first(limit = 1)
-
2377
if limit == 0
-
''
-
2377
elsif limit >= size
-
2
self
-
else
-
2375
to(limit - 1)
-
end
-
end
-
-
# Returns the last character of the string. If a limit is supplied, returns a substring
-
# from the end of the string until it reaches the limit value (counting backwards). If
-
# the given limit is greater than or equal to the string length, returns self.
-
#
-
# str = "hello"
-
# str.last #=> "o"
-
# str.last(1) #=> "o"
-
# str.last(2) #=> "lo"
-
# str.last(0) #=> ""
-
# str.last(6) #=> "hello"
-
1
def last(limit = 1)
-
27
if limit == 0
-
''
-
27
elsif limit >= size
-
2
self
-
else
-
25
from(-limit)
-
end
-
end
-
end
-
1
class String
-
# Enable more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
-
1
def acts_like_string?
-
true
-
end
-
end
-
1
require 'date'
-
1
require 'active_support/core_ext/time/calculations'
-
-
1
class String
-
# Converts a string to a Time value.
-
# The +form+ can be either :utc or :local (default :utc).
-
#
-
# The time is parsed using Date._parse method.
-
# If +form+ is :local, then time is formatted using Time.zone
-
#
-
# "3-2-2012".to_time # => 2012-02-03 00:00:00 UTC
-
# "12:20".to_time # => ArgumentError: invalid date
-
# "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 UTC
-
# "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 UTC
-
# "2012-12-13T06:12".to_time(:local) # => 2012-12-13 06:12:00 +0100
-
1
def to_time(form = :utc)
-
unless blank?
-
date_values = ::Date._parse(self, false).
-
values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).
-
map! { |arg| arg || 0 }
-
date_values[6] *= 1000000
-
offset = date_values.pop
-
-
::Time.send("#{form}_time", *date_values) - offset
-
end
-
end
-
-
# Converts a string to a Date value.
-
#
-
# "1-1-2012".to_date #=> Sun, 01 Jan 2012
-
# "01/01/2012".to_date #=> Sun, 01 Jan 2012
-
# "2012-12-13".to_date #=> Thu, 13 Dec 2012
-
# "12/13/2012".to_date #=> ArgumentError: invalid date
-
1
def to_date
-
unless blank?
-
date_values = ::Date._parse(self, false).values_at(:year, :mon, :mday)
-
-
::Date.new(*date_values)
-
end
-
end
-
-
# Converts a string to a DateTime value.
-
#
-
# "1-1-2012".to_datetime #=> Sun, 01 Jan 2012 00:00:00 +0000
-
# "01/01/2012 23:59:59".to_datetime #=> Sun, 01 Jan 2012 23:59:59 +0000
-
# "2012-12-13 12:50".to_datetime #=> Thu, 13 Dec 2012 12:50:00 +0000
-
# "12/13/2012".to_datetime #=> ArgumentError: invalid date
-
1
def to_datetime
-
unless blank?
-
date_values = ::Date._parse(self, false).
-
values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction).
-
map! { |arg| arg || 0 }
-
date_values[5] += date_values.pop
-
-
::DateTime.civil(*date_values)
-
end
-
end
-
end
-
1
class String
-
# Returns the string, first removing all whitespace on both ends of
-
# the string, and then changing remaining consecutive whitespace
-
# groups into one space each.
-
#
-
# %{ Multi-line
-
# string }.squish # => "Multi-line string"
-
# " foo bar \n \t boo".squish # => "foo bar boo"
-
1
def squish
-
dup.squish!
-
end
-
-
# Performs a destructive squish. See String#squish.
-
1
def squish!
-
strip!
-
gsub!(/\s+/, ' ')
-
self
-
end
-
-
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
-
#
-
# 'Once upon a time in a world far far away'.truncate(27)
-
# # => "Once upon a time in a wo..."
-
#
-
# Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break:
-
#
-
# 'Once upon a time in a world far far away'.truncate(27, separator: ' ')
-
# # => "Once upon a time in a..."
-
#
-
# 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
-
# # => "Once upon a time in a..."
-
#
-
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
-
# for a total length not exceeding <tt>length</tt>:
-
#
-
# 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
-
# # => "And they f... (continued)"
-
1
def truncate(truncate_at, options = {})
-
21
return dup unless length > truncate_at
-
-
18
options[:omission] ||= '...'
-
18
length_with_room_for_omission = truncate_at - options[:omission].length
-
18
stop = \
-
if options[:separator]
-
3
rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
-
else
-
15
length_with_room_for_omission
-
end
-
-
18
self[0...stop] + options[:omission]
-
end
-
end
-
1
require 'active_support/inflector/methods'
-
1
require 'active_support/inflector/transliterate'
-
-
# String inflections define new methods on the String class to transform names for different purposes.
-
# For instance, you can figure out the name of a table from the name of a class.
-
#
-
# 'ScaleScore'.tableize # => "scale_scores"
-
#
-
1
class String
-
# Returns the plural form of the word in the string.
-
#
-
# If the optional parameter +count+ is specified,
-
# the singular form will be returned if <tt>count == 1</tt>.
-
# For any other value of +count+ the plural will be returned.
-
#
-
# If the optional parameter +locale+ is specified,
-
# the word will be pluralized as a word of that language.
-
# By default, this parameter is set to <tt>:en</tt>.
-
# You must define your own inflection rules for languages other than English.
-
#
-
# 'post'.pluralize # => "posts"
-
# 'octopus'.pluralize # => "octopi"
-
# 'sheep'.pluralize # => "sheep"
-
# 'words'.pluralize # => "words"
-
# 'the blue mailman'.pluralize # => "the blue mailmen"
-
# 'CamelOctopus'.pluralize # => "CamelOctopi"
-
# 'apple'.pluralize(1) # => "apple"
-
# 'apple'.pluralize(2) # => "apples"
-
# 'ley'.pluralize(:es) # => "leyes"
-
# 'ley'.pluralize(1, :es) # => "ley"
-
1
def pluralize(count = nil, locale = :en)
-
139
locale = count if count.is_a?(Symbol)
-
139
if count == 1
-
self
-
else
-
139
ActiveSupport::Inflector.pluralize(self, locale)
-
end
-
end
-
-
# The reverse of +pluralize+, returns the singular form of a word in a string.
-
#
-
# If the optional parameter +locale+ is specified,
-
# the word will be singularized as a word of that language.
-
# By default, this paramter is set to <tt>:en</tt>.
-
# You must define your own inflection rules for languages other than English.
-
#
-
# 'posts'.singularize # => "post"
-
# 'octopi'.singularize # => "octopus"
-
# 'sheep'.singularize # => "sheep"
-
# 'word'.singularize # => "word"
-
# 'the blue mailmen'.singularize # => "the blue mailman"
-
# 'CamelOctopi'.singularize # => "CamelOctopus"
-
# 'leyes'.singularize(:es) # => "ley"
-
1
def singularize(locale = :en)
-
691
ActiveSupport::Inflector.singularize(self, locale)
-
end
-
-
# +constantize+ tries to find a declared constant with the name specified
-
# in the string. It raises a NameError when the name is not in CamelCase
-
# or is not initialized. See ActiveSupport::Inflector.constantize
-
#
-
# 'Module'.constantize # => Module
-
# 'Class'.constantize # => Class
-
# 'blargle'.constantize # => NameError: wrong constant name blargle
-
1
def constantize
-
957
ActiveSupport::Inflector.constantize(self)
-
end
-
-
# +safe_constantize+ tries to find a declared constant with the name specified
-
# in the string. It returns nil when the name is not in CamelCase
-
# or is not initialized. See ActiveSupport::Inflector.safe_constantize
-
#
-
# 'Module'.safe_constantize # => Module
-
# 'Class'.safe_constantize # => Class
-
# 'blargle'.safe_constantize # => nil
-
1
def safe_constantize
-
44
ActiveSupport::Inflector.safe_constantize(self)
-
end
-
-
# By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
-
# is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
-
#
-
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
-
#
-
# 'active_record'.camelize # => "ActiveRecord"
-
# 'active_record'.camelize(:lower) # => "activeRecord"
-
# 'active_record/errors'.camelize # => "ActiveRecord::Errors"
-
# 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"
-
1
def camelize(first_letter = :upper)
-
2100
case first_letter
-
when :upper
-
2100
ActiveSupport::Inflector.camelize(self, true)
-
when :lower
-
ActiveSupport::Inflector.camelize(self, false)
-
end
-
end
-
1
alias_method :camelcase, :camelize
-
-
# Capitalizes all the words and replaces some characters in the string to create
-
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
-
# used in the Rails internals.
-
#
-
# +titleize+ is also aliased as +titlecase+.
-
#
-
# 'man from the boondocks'.titleize # => "Man From The Boondocks"
-
# 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
-
1
def titleize
-
ActiveSupport::Inflector.titleize(self)
-
end
-
1
alias_method :titlecase, :titleize
-
-
# The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
-
#
-
# +underscore+ will also change '::' to '/' to convert namespaces to paths.
-
#
-
# 'ActiveModel'.underscore # => "active_model"
-
# 'ActiveModel::Errors'.underscore # => "active_model/errors"
-
1
def underscore
-
6138
ActiveSupport::Inflector.underscore(self)
-
end
-
-
# Replaces underscores with dashes in the string.
-
#
-
# 'puni_puni'.dasherize # => "puni-puni"
-
1
def dasherize
-
16649
ActiveSupport::Inflector.dasherize(self)
-
end
-
-
# Removes the module part from the constant expression in the string.
-
#
-
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
-
# 'Inflections'.demodulize # => "Inflections"
-
#
-
# See also +deconstantize+.
-
1
def demodulize
-
42
ActiveSupport::Inflector.demodulize(self)
-
end
-
-
# Removes the rightmost segment from the constant expression in the string.
-
#
-
# 'Net::HTTP'.deconstantize # => "Net"
-
# '::Net::HTTP'.deconstantize # => "::Net"
-
# 'String'.deconstantize # => ""
-
# '::String'.deconstantize # => ""
-
# ''.deconstantize # => ""
-
#
-
# See also +demodulize+.
-
1
def deconstantize
-
ActiveSupport::Inflector.deconstantize(self)
-
end
-
-
# Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
-
#
-
# class Person
-
# def to_param
-
# "#{id}-#{name.parameterize}"
-
# end
-
# end
-
#
-
# @person = Person.find(1)
-
# # => #<Person id: 1, name: "Donald E. Knuth">
-
#
-
# <%= link_to(@person.name, person_path) %>
-
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
-
1
def parameterize(sep = '-')
-
ActiveSupport::Inflector.parameterize(self, sep)
-
end
-
-
# Creates the name of a table like Rails does for models to table names. This method
-
# uses the +pluralize+ method on the last word in the string.
-
#
-
# 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
-
# 'egg_and_ham'.tableize # => "egg_and_hams"
-
# 'fancyCategory'.tableize # => "fancy_categories"
-
1
def tableize
-
ActiveSupport::Inflector.tableize(self)
-
end
-
-
# Create a class name from a plural table name like Rails does for table names to models.
-
# Note that this returns a string and not a class. (To convert to an actual class
-
# follow +classify+ with +constantize+.)
-
#
-
# 'egg_and_hams'.classify # => "EggAndHam"
-
# 'posts'.classify # => "Post"
-
#
-
# Singular names are not handled correctly.
-
#
-
# 'business'.classify # => "Busines"
-
1
def classify
-
36
ActiveSupport::Inflector.classify(self)
-
end
-
-
# Capitalizes the first word, turns underscores into spaces, and strips '_id'.
-
# Like +titleize+, this is meant for creating pretty output.
-
#
-
# 'employee_salary' # => "Employee salary"
-
# 'author_id' # => "Author"
-
1
def humanize
-
57
ActiveSupport::Inflector.humanize(self)
-
end
-
-
# Creates a foreign key name from a class name.
-
# +separate_class_name_and_id_with_underscore+ sets whether
-
# the method should put '_' between the name and 'id'.
-
#
-
# 'Message'.foreign_key # => "message_id"
-
# 'Message'.foreign_key(false) # => "messageid"
-
# 'Admin::Post'.foreign_key # => "post_id"
-
1
def foreign_key(separate_class_name_and_id_with_underscore = true)
-
ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
-
end
-
end
-
# encoding: utf-8
-
1
require 'active_support/multibyte'
-
-
1
class String
-
# == Multibyte proxy
-
#
-
# +mb_chars+ is a multibyte safe proxy for string methods.
-
#
-
# In Ruby 1.8 and older it creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
-
# encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
-
# class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
-
#
-
# name = 'Claus Müller'
-
# name.reverse # => "rell??M sualC"
-
# name.length # => 13
-
#
-
# name.mb_chars.reverse.to_s # => "rellüM sualC"
-
# name.mb_chars.length # => 12
-
#
-
# In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
-
# it becomes easy to run one version of your code on multiple Ruby versions.
-
#
-
# == Method chaining
-
#
-
# All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
-
# method chaining on the result of any of these methods.
-
#
-
# name.mb_chars.reverse.length # => 12
-
#
-
# == Interoperability and configuration
-
#
-
# The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
-
# String and Char work like expected. The bang! methods change the internal string representation in the Chars
-
# object. Interoperability problems can be resolved easily with a +to_s+ call.
-
#
-
# For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
-
# information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
-
1
def mb_chars
-
if ActiveSupport::Multibyte.proxy_class.consumes?(self)
-
ActiveSupport::Multibyte.proxy_class.new(self)
-
else
-
self
-
end
-
end
-
-
1
def is_utf8?
-
case encoding
-
when Encoding::UTF_8
-
valid_encoding?
-
when Encoding::ASCII_8BIT, Encoding::US_ASCII
-
dup.force_encoding(Encoding::UTF_8).valid_encoding?
-
else
-
false
-
end
-
end
-
end
-
1
require 'erb'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
-
1
class ERB
-
1
module Util
-
1
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
-
1
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
-
1
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
-
1
JSON_ESCAPE_REGEXP = /[&"><]/
-
-
# A utility method for escaping HTML tag characters.
-
# This method is also aliased as <tt>h</tt>.
-
#
-
# In your ERB templates, use this method to escape any unsafe content. For example:
-
# <%=h @person.name %>
-
#
-
# puts html_escape('is a > 0 & a < 10?')
-
# # => is a > 0 & a < 10?
-
1
def html_escape(s)
-
39661
s = s.to_s
-
39661
if s.html_safe?
-
1578
s
-
else
-
38083
s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe
-
end
-
end
-
-
# Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
-
1
remove_method(:h)
-
1
alias h html_escape
-
-
1
module_function :h
-
-
1
singleton_class.send(:remove_method, :html_escape)
-
1
module_function :html_escape
-
-
# A utility method for escaping HTML without affecting existing escaped entities.
-
#
-
# html_escape_once('1 < 2 & 3')
-
# # => "1 < 2 & 3"
-
#
-
# html_escape_once('<< Accept & Checkout')
-
# # => "<< Accept & Checkout"
-
1
def html_escape_once(s)
-
12
result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] }
-
4
s.html_safe? ? result.html_safe : result
-
end
-
-
1
module_function :html_escape_once
-
-
# A utility method for escaping HTML entities in JSON strings
-
# using \uXXXX JavaScript escape sequences for string literals:
-
#
-
# json_escape('is a > 0 & a < 10?')
-
# # => is a \u003E 0 \u0026 a \u003C 10?
-
#
-
# Note that after this operation is performed the output is not
-
# valid JSON. In particular double quotes are removed:
-
#
-
# json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
-
# # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
-
1
def json_escape(s)
-
8
result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] }
-
5
s.html_safe? ? result.html_safe : result
-
end
-
-
1
module_function :json_escape
-
end
-
end
-
-
1
class Object
-
1
def html_safe?
-
41109
false
-
end
-
end
-
-
1
class Numeric
-
1
def html_safe?
-
true
-
end
-
end
-
-
1
module ActiveSupport #:nodoc:
-
1
class SafeBuffer < String
-
1
UNSAFE_STRING_METHODS = %w(
-
capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
-
slice squeeze strip sub succ swapcase tr tr_s upcase prepend
-
)
-
-
1
alias_method :original_concat, :concat
-
1
private :original_concat
-
-
1
class SafeConcatError < StandardError
-
1
def initialize
-
super 'Could not concatenate to the buffer because it is not html safe.'
-
end
-
end
-
-
1
def [](*args)
-
4
if args.size < 2
-
2
super
-
else
-
2
if html_safe?
-
2
new_safe_buffer = super
-
4
new_safe_buffer.instance_eval { @html_safe = true }
-
2
new_safe_buffer
-
else
-
to_str[*args]
-
end
-
end
-
end
-
-
1
def safe_concat(value)
-
2118
raise SafeConcatError unless html_safe?
-
2118
original_concat(value)
-
end
-
-
1
def initialize(*)
-
76279
@html_safe = true
-
76279
super
-
end
-
-
1
def initialize_copy(other)
-
2638
super
-
2638
@html_safe = other.html_safe?
-
end
-
-
1
def clone_empty
-
2
self[0, 0]
-
end
-
-
1
def concat(value)
-
4553
if !html_safe? || value.html_safe?
-
1610
super(value)
-
else
-
2943
super(ERB::Util.h(value))
-
end
-
end
-
1
alias << concat
-
-
1
def +(other)
-
2628
dup.concat(other)
-
end
-
-
1
def %(args)
-
args = Array(args).map do |arg|
-
if !html_safe? || arg.html_safe?
-
arg
-
else
-
ERB::Util.h(arg)
-
end
-
end
-
-
self.class.new(super(args))
-
end
-
-
1
def html_safe?
-
12604
defined?(@html_safe) && @html_safe
-
end
-
-
1
def to_s
-
4298
self
-
end
-
-
1
def to_param
-
1
to_str
-
end
-
-
1
def encode_with(coder)
-
coder.represent_scalar nil, to_str
-
end
-
-
1
UNSAFE_STRING_METHODS.each do |unsafe_method|
-
20
if 'String'.respond_to?(unsafe_method)
-
20
class_eval <<-EOT, __FILE__, __LINE__ + 1
-
def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
-
to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
-
end # end
-
-
def #{unsafe_method}!(*args) # def capitalize!(*args)
-
@html_safe = false # @html_safe = false
-
super # super
-
end # end
-
EOT
-
end
-
end
-
end
-
end
-
-
1
class String
-
1
def html_safe
-
73733
ActiveSupport::SafeBuffer.new(self)
-
end
-
end
-
1
class String
-
1
alias_method :starts_with?, :start_with?
-
1
alias_method :ends_with?, :end_with?
-
end
-
1
require 'active_support/core_ext/object/acts_like'
-
-
1
class Time
-
# Duck-types as a Time-like class. See Object#acts_like?.
-
1
def acts_like_time?
-
true
-
end
-
end
-
1
require 'active_support/duration'
-
1
require 'active_support/core_ext/time/conversions'
-
1
require 'active_support/time_with_zone'
-
1
require 'active_support/core_ext/time/zones'
-
1
require 'active_support/core_ext/date_and_time/calculations'
-
-
1
class Time
-
1
include DateAndTime::Calculations
-
-
1
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
-
-
1
class << self
-
# Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances
-
1
def ===(other)
-
5
super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone))
-
end
-
-
# Return the number of days in the given month.
-
# If no year is specified, it will use the current year.
-
1
def days_in_month(month, year = now.year)
-
if month == 2 && ::Date.gregorian_leap?(year)
-
29
-
else
-
COMMON_YEAR_DAYS_IN_MONTH[month]
-
end
-
end
-
-
# Returns a new Time if requested year can be accommodated by Ruby's Time class
-
# (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture);
-
# otherwise returns a DateTime.
-
1
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
-
16
time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
-
-
# This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138.
-
16
if time.year == year
-
16
time
-
else
-
::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
-
end
-
rescue
-
::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
-
end
-
-
# Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:utc</tt>.
-
1
def utc_time(*args)
-
10
time_with_datetime_fallback(:utc, *args)
-
end
-
-
# Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:local</tt>.
-
1
def local_time(*args)
-
6
time_with_datetime_fallback(:local, *args)
-
end
-
-
# Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>.
-
1
def current
-
19
::Time.zone ? ::Time.zone.now : ::Time.now
-
end
-
end
-
-
# Seconds since midnight: Time.now.seconds_since_midnight
-
1
def seconds_since_midnight
-
to_i - change(:hour => 0).to_i + (usec / 1.0e+6)
-
end
-
-
# Returns a new Time where one or more of the elements have been changed according
-
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
-
# <tt>:sec</tt>, <tt>:usec</tt>) reset cascadingly, so if only the hour is passed,
-
# then minute, sec, and usec is set to 0. If the hour and minute is passed, then
-
# sec and usec is set to 0. The +options+ parameter takes a hash with any of these
-
# keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>,
-
# <tt>:sec</tt>, <tt>:usec</tt>.
-
#
-
# Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
-
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
-
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
-
1
def change(options)
-
281
new_year = options.fetch(:year, year)
-
281
new_month = options.fetch(:month, month)
-
281
new_day = options.fetch(:day, day)
-
281
new_hour = options.fetch(:hour, hour)
-
281
new_min = options.fetch(:min, options[:hour] ? 0 : min)
-
281
new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
-
281
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
-
-
281
if utc?
-
267
::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
-
14
elsif zone
-
14
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
-
else
-
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
-
end
-
end
-
-
# Uses Date to provide precise Time calculations for years, months, and days.
-
# The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
-
# <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
-
# <tt>:minutes</tt>, <tt>:seconds</tt>.
-
1
def advance(options)
-
253
unless options[:weeks].nil?
-
options[:weeks], partial_weeks = options[:weeks].divmod(1)
-
options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
-
end
-
-
253
unless options[:days].nil?
-
97
options[:days], partial_days = options[:days].divmod(1)
-
97
options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
-
end
-
-
253
d = to_date.advance(options)
-
253
time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
-
253
seconds_to_advance = \
-
options.fetch(:seconds, 0) +
-
options.fetch(:minutes, 0) * 60 +
-
options.fetch(:hours, 0) * 3600
-
-
253
if seconds_to_advance.zero?
-
253
time_advanced_by_date
-
else
-
time_advanced_by_date.since(seconds_to_advance)
-
end
-
end
-
-
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
-
1
def ago(seconds)
-
since(-seconds)
-
end
-
-
# Returns a new Time representing the time a number of seconds since the instance time
-
1
def since(seconds)
-
318
self + seconds
-
rescue
-
to_datetime.since(seconds)
-
end
-
1
alias :in :since
-
-
# Returns a new Time representing the start of the day (0:00)
-
1
def beginning_of_day
-
#(self - seconds_since_midnight).change(usec: 0)
-
22
change(:hour => 0)
-
end
-
1
alias :midnight :beginning_of_day
-
1
alias :at_midnight :beginning_of_day
-
1
alias :at_beginning_of_day :beginning_of_day
-
-
# Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
-
1
def end_of_day
-
change(
-
:hour => 23,
-
:min => 59,
-
:sec => 59,
-
:usec => Rational(999999999, 1000)
-
)
-
end
-
-
# Returns a new Time representing the start of the hour (x:00)
-
1
def beginning_of_hour
-
change(:min => 0)
-
end
-
1
alias :at_beginning_of_hour :beginning_of_hour
-
-
# Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
-
1
def end_of_hour
-
change(
-
:min => 59,
-
:sec => 59,
-
:usec => Rational(999999999, 1000)
-
)
-
end
-
-
# Returns a Range representing the whole day of the current time.
-
1
def all_day
-
beginning_of_day..end_of_day
-
end
-
-
# Returns a Range representing the whole week of the current time.
-
# Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
-
1
def all_week(start_day = Date.beginning_of_week)
-
beginning_of_week(start_day)..end_of_week(start_day)
-
end
-
-
# Returns a Range representing the whole month of the current time.
-
1
def all_month
-
beginning_of_month..end_of_month
-
end
-
-
# Returns a Range representing the whole quarter of the current time.
-
1
def all_quarter
-
beginning_of_quarter..end_of_quarter
-
end
-
-
# Returns a Range representing the whole year of the current time.
-
1
def all_year
-
beginning_of_year..end_of_year
-
end
-
-
1
def plus_with_duration(other) #:nodoc:
-
909
if ActiveSupport::Duration === other
-
510
other.since(self)
-
else
-
399
plus_without_duration(other)
-
end
-
end
-
1
alias_method :plus_without_duration, :+
-
1
alias_method :+, :plus_with_duration
-
-
1
def minus_with_duration(other) #:nodoc:
-
6423
if ActiveSupport::Duration === other
-
49
other.until(self)
-
else
-
6374
minus_without_duration(other)
-
end
-
end
-
1
alias_method :minus_without_duration, :-
-
1
alias_method :-, :minus_with_duration
-
-
# Time#- can also be used to determine the number of seconds between two Time instances.
-
# We're layering on additional behavior so that ActiveSupport::TimeWithZone instances
-
# are coerced into values that Time#- will recognize
-
1
def minus_with_coercion(other)
-
6423
other = other.comparable_time if other.respond_to?(:comparable_time)
-
6423
other.is_a?(DateTime) ? to_f - other.to_f : minus_without_coercion(other)
-
end
-
1
alias_method :minus_without_coercion, :-
-
1
alias_method :-, :minus_with_coercion
-
-
# Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances
-
# can be chronologically compared with a Time
-
1
def compare_with_coercion(other)
-
# we're avoiding Time#to_datetime cause it's expensive
-
712
if other.is_a?(Time)
-
627
compare_without_coercion(other.to_time)
-
else
-
85
to_datetime <=> other
-
end
-
end
-
1
alias_method :compare_without_coercion, :<=>
-
1
alias_method :<=>, :compare_with_coercion
-
-
# Layers additional behavior on Time#eql? so that ActiveSupport::TimeWithZone instances
-
# can be eql? to an equivalent Time
-
1
def eql_with_coercion(other)
-
# if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do eql? comparison
-
other = other.comparable_time if other.respond_to?(:comparable_time)
-
eql_without_coercion(other)
-
end
-
1
alias_method :eql_without_coercion, :eql?
-
1
alias_method :eql?, :eql_with_coercion
-
-
end
-
1
require 'active_support/inflector/methods'
-
1
require 'active_support/values/time_zone'
-
-
1
class Time
-
1
DATE_FORMATS = {
-
:db => '%Y-%m-%d %H:%M:%S',
-
:number => '%Y%m%d%H%M%S',
-
:nsec => '%Y%m%d%H%M%S%9N',
-
:time => '%H:%M',
-
:short => '%d %b %H:%M',
-
:long => '%B %d, %Y %H:%M',
-
:long_ordinal => lambda { |time|
-
day_format = ActiveSupport::Inflector.ordinalize(time.day)
-
time.strftime("%B #{day_format}, %Y %H:%M")
-
},
-
:rfc822 => lambda { |time|
-
offset_format = time.formatted_offset(false)
-
time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
-
}
-
}
-
-
# Converts to a formatted string. See DATE_FORMATS for builtin formats.
-
#
-
# This method is aliased to <tt>to_s</tt>.
-
#
-
# time = Time.now # => Thu Jan 18 06:10:17 CST 2007
-
#
-
# time.to_formatted_s(:time) # => "06:10"
-
# time.to_s(:time) # => "06:10"
-
#
-
# time.to_formatted_s(:db) # => "2007-01-18 06:10:17"
-
# time.to_formatted_s(:number) # => "20070118061017"
-
# time.to_formatted_s(:short) # => "18 Jan 06:10"
-
# time.to_formatted_s(:long) # => "January 18, 2007 06:10"
-
# time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10"
-
# time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
-
#
-
# == Adding your own time formats to +to_formatted_s+
-
# You can add your own formats to the Time::DATE_FORMATS hash.
-
# Use the format name as the hash key and either a strftime string
-
# or Proc instance that takes a time argument as the value.
-
#
-
# # config/initializers/time_formats.rb
-
# Time::DATE_FORMATS[:month_and_year] = '%B %Y'
-
# Time::DATE_FORMATS[:short_ordinal] = ->(time) { time.strftime("%B #{time.day.ordinalize}") }
-
1
def to_formatted_s(format = :default)
-
6
if formatter = DATE_FORMATS[format]
-
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
-
else
-
6
to_default_s
-
end
-
end
-
1
alias_method :to_default_s, :to_s
-
1
alias_method :to_s, :to_formatted_s
-
-
# Returns the UTC offset as an +HH:MM formatted string.
-
#
-
# Time.local(2000).formatted_offset # => "-06:00"
-
# Time.local(2000).formatted_offset(false) # => "-0600"
-
1
def formatted_offset(colon = true, alternate_utc_string = nil)
-
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
-
end
-
end
-
# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only
-
# preserves utc_offset. Preserve zone also, even though it may not
-
# work in some edge cases.
-
1
if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
-
1
class Time
-
1
class << self
-
1
alias_method :_load_without_zone, :_load
-
1
def _load(marshaled_time)
-
time = _load_without_zone(marshaled_time)
-
time.instance_eval do
-
if zone = defined?(@_zone) && remove_instance_variable('@_zone')
-
ary = to_a
-
ary[0] += subsec if ary[0] == sec
-
ary[-1] = zone
-
utc? ? Time.utc(*ary) : Time.local(*ary)
-
else
-
self
-
end
-
end
-
end
-
end
-
-
1
alias_method :_dump_without_zone, :_dump
-
1
def _dump(*args)
-
obj = dup
-
obj.instance_variable_set('@_zone', zone)
-
obj._dump_without_zone(*args)
-
end
-
end
-
end
-
1
require 'active_support/time_with_zone'
-
-
1
class Time
-
1
@zone_default = nil
-
-
1
class << self
-
1
attr_accessor :zone_default
-
-
# Returns the TimeZone for the current request, if this has been set (via Time.zone=).
-
# If <tt>Time.zone</tt> has not been set for the current request, returns the TimeZone specified in <tt>config.time_zone</tt>.
-
1
def zone
-
33
Thread.current[:time_zone] || zone_default
-
end
-
-
# Sets <tt>Time.zone</tt> to a TimeZone object for the current request/thread.
-
#
-
# This method accepts any of the following:
-
#
-
# * A Rails TimeZone object.
-
# * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
-
# * A TZInfo::Timezone object.
-
# * An identifier for a TZInfo::Timezone object (e.g., "America/New_York").
-
#
-
# Here's an example of how you might set <tt>Time.zone</tt> on a per request basis and reset it when the request is done.
-
# <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
-
#
-
# class ApplicationController < ActionController::Base
-
# around_filter :set_time_zone
-
#
-
# def set_time_zone
-
# if logged_in?
-
# Time.use_zone(current_user.time_zone) { yield }
-
# else
-
# yield
-
# end
-
# end
-
# end
-
1
def zone=(time_zone)
-
14
Thread.current[:time_zone] = find_zone!(time_zone)
-
end
-
-
# Allows override of <tt>Time.zone</tt> locally inside supplied block; resets <tt>Time.zone</tt> to existing value when done.
-
1
def use_zone(time_zone)
-
new_zone = find_zone!(time_zone)
-
begin
-
old_zone, ::Time.zone = ::Time.zone, new_zone
-
yield
-
ensure
-
::Time.zone = old_zone
-
end
-
end
-
-
# Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones.
-
1
def find_zone!(time_zone)
-
249
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
-
239
time_zone
-
else
-
# lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
-
10
unless time_zone.respond_to?(:period_for_local)
-
10
time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
-
end
-
-
# Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
-
10
if time_zone.is_a?(ActiveSupport::TimeZone)
-
10
time_zone
-
else
-
ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
-
end
-
end
-
rescue TZInfo::InvalidTimezoneIdentifier
-
raise ArgumentError, "Invalid Timezone: #{time_zone}"
-
end
-
-
1
def find_zone(time_zone)
-
find_zone!(time_zone) rescue nil
-
end
-
end
-
-
# Returns the simultaneous time in <tt>Time.zone</tt>.
-
#
-
# Time.zone = 'Hawaii' # => 'Hawaii'
-
# Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
-
# instead of the operating system's time zone.
-
#
-
# You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument,
-
# and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
-
#
-
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
-
1
def in_time_zone(zone = ::Time.zone)
-
235
if zone
-
235
ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
-
else
-
self
-
end
-
end
-
end
-
# encoding: utf-8
-
-
1
require 'uri'
-
1
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
-
1
parser = URI::Parser.new
-
-
1
unless str == parser.unescape(parser.escape(str))
-
1
URI::Parser.class_eval do
-
1
remove_method :unescape
-
1
def unescape(str, escaped = /%[a-fA-F\d]{2}/)
-
# TODO: Are we actually sure that ASCII == UTF-8?
-
# YK: My initial experiments say yes, but let's be sure please
-
9160
enc = str.encoding
-
9160
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
-
9297
str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
-
end
-
end
-
end
-
-
1
module URI
-
1
class << self
-
1
def parser
-
13974
@parser ||= URI::Parser.new
-
end
-
end
-
end
-
1
require 'set'
-
1
require 'thread'
-
1
require 'pathname'
-
1
require 'active_support/core_ext/module/aliasing'
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
1
require 'active_support/core_ext/module/introspection'
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/core_ext/module/qualified_const'
-
1
require 'active_support/core_ext/object/blank'
-
1
require 'active_support/core_ext/load_error'
-
1
require 'active_support/core_ext/name_error'
-
1
require 'active_support/core_ext/string/starts_ends_with'
-
1
require 'active_support/inflector'
-
-
1
module ActiveSupport #:nodoc:
-
1
module Dependencies #:nodoc:
-
1
extend self
-
-
# Should we turn on Ruby warnings on the first load of dependent files?
-
1
mattr_accessor :warnings_on_first_load
-
1
self.warnings_on_first_load = false
-
-
# All files ever loaded.
-
1
mattr_accessor :history
-
1
self.history = Set.new
-
-
# All files currently loaded.
-
1
mattr_accessor :loaded
-
1
self.loaded = Set.new
-
-
# Should we load files or require them?
-
1
mattr_accessor :mechanism
-
1
self.mechanism = ENV['NO_RELOAD'] ? :require : :load
-
-
# The set of directories from which we may automatically load files. Files
-
# under these directories will be reloaded on each request in development mode,
-
# unless the directory also appears in autoload_once_paths.
-
1
mattr_accessor :autoload_paths
-
1
self.autoload_paths = []
-
-
# The set of directories from which automatically loaded constants are loaded
-
# only once. All directories in this set must also be present in +autoload_paths+.
-
1
mattr_accessor :autoload_once_paths
-
1
self.autoload_once_paths = []
-
-
# An array of qualified constant names that have been loaded. Adding a name
-
# to this array will cause it to be unloaded the next time Dependencies are
-
# cleared.
-
1
mattr_accessor :autoloaded_constants
-
1
self.autoloaded_constants = []
-
-
# An array of constant names that need to be unloaded on every request. Used
-
# to allow arbitrary constants to be marked for unloading.
-
1
mattr_accessor :explicitly_unloadable_constants
-
1
self.explicitly_unloadable_constants = []
-
-
# The logger is used for generating information on the action run-time
-
# (including benchmarking) if available. Can be set to nil for no logging.
-
# Compatible with both Ruby's own Logger and Log4r loggers.
-
1
mattr_accessor :logger
-
-
# Set to +true+ to enable logging of const_missing and file loads.
-
1
mattr_accessor :log_activity
-
1
self.log_activity = false
-
-
# The WatchStack keeps a stack of the modules being watched as files are
-
# loaded. If a file in the process of being loaded (parent.rb) triggers the
-
# load of another file (child.rb) the stack will ensure that child.rb
-
# handles the new constants.
-
#
-
# If child.rb is being autoloaded, its constants will be added to
-
# autoloaded_constants. If it was being `require`d, they will be discarded.
-
#
-
# This is handled by walking back up the watch stack and adding the constants
-
# found by child.rb to the list of original constants in parent.rb.
-
1
class WatchStack
-
1
include Enumerable
-
-
# @watching is a stack of lists of constants being watched. For instance,
-
# if parent.rb is autoloaded, the stack will look like [[Object]]. If
-
# parent.rb then requires namespace/child.rb, the stack will look like
-
# [[Object], [Namespace]].
-
-
1
def initialize
-
1
@watching = []
-
1
@stack = Hash.new { |h,k| h[k] = [] }
-
end
-
-
1
def each(&block)
-
@stack.each(&block)
-
end
-
-
1
def watching?
-
3254
!@watching.empty?
-
end
-
-
# Returns a list of new constants found since the last call to
-
# <tt>watch_namespaces</tt>.
-
1
def new_constants
-
361
constants = []
-
-
# Grab the list of namespaces that we're looking for new constants under
-
361
@watching.last.each do |namespace|
-
# Retrieve the constants that were present under the namespace when watch_namespaces
-
# was originally called
-
original_constants = @stack[namespace].last
-
-
mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace)
-
next unless mod.is_a?(Module)
-
-
# Get a list of the constants that were added
-
new_constants = mod.local_constants - original_constants
-
-
# self[namespace] returns an Array of the constants that are being evaluated
-
# for that namespace. For instance, if parent.rb requires child.rb, the first
-
# element of self[Object] will be an Array of the constants that were present
-
# before parent.rb was required. The second element will be an Array of the
-
# constants that were present before child.rb was required.
-
@stack[namespace].each do |namespace_constants|
-
namespace_constants.concat(new_constants)
-
end
-
-
# Normalize the list of new constants, and add them to the list we will return
-
new_constants.each do |suffix|
-
constants << ([namespace, suffix] - ["Object"]).join("::")
-
end
-
end
-
361
constants
-
ensure
-
# A call to new_constants is always called after a call to watch_namespaces
-
361
pop_modules(@watching.pop)
-
end
-
-
# Add a set of modules to the watch stack, remembering the initial
-
# constants.
-
1
def watch_namespaces(namespaces)
-
@watching << namespaces.map do |namespace|
-
module_name = Dependencies.to_constant_name(namespace)
-
original_constants = Dependencies.qualified_const_defined?(module_name) ?
-
Inflector.constantize(module_name).local_constants : []
-
-
@stack[module_name] << original_constants
-
module_name
-
361
end
-
end
-
-
1
private
-
1
def pop_modules(modules)
-
361
modules.each { |mod| @stack[mod].pop }
-
end
-
end
-
-
# An internal stack used to record which constants are loaded by any block.
-
1
mattr_accessor :constant_watch_stack
-
1
self.constant_watch_stack = WatchStack.new
-
-
# Module includes this module.
-
1
module ModuleConstMissing #:nodoc:
-
1
def self.append_features(base)
-
2
base.class_eval do
-
# Emulate #exclude via an ivar
-
2
return if defined?(@_const_missing) && @_const_missing
-
1
@_const_missing = instance_method(:const_missing)
-
1
remove_method(:const_missing)
-
end
-
1
super
-
end
-
-
1
def self.exclude_from(base)
-
base.class_eval do
-
define_method :const_missing, @_const_missing
-
@_const_missing = nil
-
end
-
end
-
-
1
def const_missing(const_name)
-
# The interpreter does not pass nesting information, and in the
-
# case of anonymous modules we cannot even make the trade-off of
-
# assuming their name reflects the nesting. Resort to Object as
-
# the only meaningful guess we can make.
-
591
from_mod = anonymous? ? ::Object : self
-
591
Dependencies.load_missing_constant(from_mod, const_name)
-
end
-
-
1
def unloadable(const_desc = self)
-
super(const_desc)
-
end
-
end
-
-
# Object includes this module.
-
1
module Loadable #:nodoc:
-
1
def self.exclude_from(base)
-
base.class_eval { define_method(:load, Kernel.instance_method(:load)) }
-
end
-
-
1
def require_or_load(file_name)
-
Dependencies.require_or_load(file_name)
-
end
-
-
1
def require_dependency(file_name, message = "No such file to load -- %s")
-
369
unless file_name.is_a?(String)
-
raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}"
-
end
-
-
369
Dependencies.depend_on(file_name, message)
-
end
-
-
1
def load_dependency(file)
-
3254
if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching?
-
Dependencies.new_constants_in(Object) { yield }
-
else
-
3254
yield
-
end
-
rescue Exception => exception # errors from loading file
-
2
exception.blame_file! file
-
2
raise
-
end
-
-
1
def load(file, wrap = false)
-
result = false
-
load_dependency(file) { result = super }
-
result
-
end
-
-
1
def require(file)
-
3254
result = false
-
6508
load_dependency(file) { result = super }
-
3252
result
-
end
-
-
# Mark the given constant as unloadable. Unloadable constants are removed
-
# each time dependencies are cleared.
-
#
-
# Note that marking a constant for unloading need only be done once. Setup
-
# or init scripts may list each unloadable constant that may need unloading;
-
# each constant will be removed for every subsequent clear, as opposed to
-
# for the first clear.
-
#
-
# The provided constant descriptor may be a (non-anonymous) module or class,
-
# or a qualified constant name as a string or symbol.
-
#
-
# Returns +true+ if the constant was not previously marked for unloading,
-
# +false+ otherwise.
-
1
def unloadable(const_desc)
-
Dependencies.mark_for_unload const_desc
-
end
-
end
-
-
# Exception file-blaming.
-
1
module Blamable #:nodoc:
-
1
def blame_file!(file)
-
2
(@blamed_files ||= []).unshift file
-
end
-
-
1
def blamed_files
-
368
@blamed_files ||= []
-
end
-
-
1
def describe_blame
-
return nil if blamed_files.empty?
-
"This error occurred while loading the following files:\n #{blamed_files.join "\n "}"
-
end
-
-
1
def copy_blame!(exc)
-
351
@blamed_files = exc.blamed_files.clone
-
351
self
-
end
-
end
-
-
1
def hook!
-
4
Object.class_eval { include Loadable }
-
4
Module.class_eval { include ModuleConstMissing }
-
4
Exception.class_eval { include Blamable }
-
end
-
-
1
def unhook!
-
ModuleConstMissing.exclude_from(Module)
-
Loadable.exclude_from(Object)
-
end
-
-
1
def load?
-
3615
mechanism == :load
-
end
-
-
1
def depend_on(file_name, message = "No such file to load -- %s.rb")
-
369
path = search_for_file(file_name)
-
369
require_or_load(path || file_name)
-
rescue LoadError => load_error
-
351
if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
-
351
load_error.message.replace(message % file_name)
-
351
load_error.copy_blame!(load_error)
-
end
-
351
raise
-
end
-
-
1
def clear
-
7
log_call
-
7
loaded.clear
-
7
remove_unloadable_constants!
-
end
-
-
1
def require_or_load(file_name, const_path = nil)
-
369
log_call file_name, const_path
-
369
file_name = $` if file_name =~ /\.rb\z/
-
369
expanded = File.expand_path(file_name)
-
369
return if loaded.include?(expanded)
-
-
# Record that we've seen this file *before* loading it to avoid an
-
# infinite loop with mutual dependencies.
-
361
loaded << expanded
-
-
361
begin
-
361
if load?
-
361
log "loading #{file_name}"
-
-
# Enable warnings if this file has not been loaded before and
-
# warnings_on_first_load is set.
-
361
load_args = ["#{file_name}.rb"]
-
361
load_args << const_path unless const_path.nil?
-
-
361
if !warnings_on_first_load or history.include?(expanded)
-
361
result = load_file(*load_args)
-
else
-
enable_warnings { result = load_file(*load_args) }
-
end
-
else
-
log "requiring #{file_name}"
-
result = require file_name
-
end
-
rescue Exception
-
351
loaded.delete expanded
-
351
raise
-
end
-
-
# Record history *after* loading so first load gets warnings.
-
10
history << expanded
-
10
result
-
end
-
-
# Is the provided constant path defined?
-
1
def qualified_const_defined?(path)
-
591
Object.qualified_const_defined?(path.sub(/^::/, ''), false)
-
end
-
-
# Given +path+, a filesystem path to a ruby file, return an array of
-
# constant paths which would cause Dependencies to attempt to load this
-
# file.
-
1
def loadable_constants_for_path(path, bases = autoload_paths)
-
361
path = $` if path =~ /\.rb\z/
-
361
expanded_path = File.expand_path(path)
-
361
paths = []
-
-
361
bases.each do |root|
-
expanded_root = File.expand_path(root)
-
next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path
-
-
nesting = expanded_path[(expanded_root.size)..-1]
-
nesting = nesting[1..-1] if nesting && nesting[0] == ?/
-
next if nesting.blank?
-
-
paths << nesting.camelize
-
end
-
-
361
paths.uniq!
-
361
paths
-
end
-
-
# Search for a file in autoload_paths matching the provided suffix.
-
1
def search_for_file(path_suffix)
-
960
path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb")
-
-
960
autoload_paths.each do |root|
-
path = File.join(root, path_suffix)
-
return path if File.file? path
-
end
-
nil # Gee, I sure wish we had first_match ;-)
-
end
-
-
# Does the provided path_suffix correspond to an autoloadable module?
-
# Instead of returning a boolean, the autoload base for this module is
-
# returned.
-
1
def autoloadable_module?(path_suffix)
-
591
autoload_paths.each do |load_path|
-
return load_path if File.directory? File.join(load_path, path_suffix)
-
end
-
nil
-
end
-
-
1
def load_once_path?(path)
-
# to_s works around a ruby1.9 issue where #starts_with?(Pathname) will always return false
-
10
autoload_once_paths.any? { |base| path.starts_with? base.to_s }
-
end
-
-
# Attempt to autoload the provided module name by searching for a directory
-
# matching the expected path suffix. If found, the module is created and
-
# assigned to +into+'s constants with the name +const_name+. Provided that
-
# the directory was loaded from a reloadable base path, it is added to the
-
# set of constants that are to be unloaded.
-
1
def autoload_module!(into, const_name, qualified_name, path_suffix)
-
591
return nil unless base_path = autoloadable_module?(path_suffix)
-
mod = Module.new
-
into.const_set const_name, mod
-
autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
-
mod
-
end
-
-
# Load the file at the provided path. +const_paths+ is a set of qualified
-
# constant names. When loading the file, Dependencies will watch for the
-
# addition of these constants. Each that is defined will be marked as
-
# autoloaded, and will be removed when Dependencies.clear is next called.
-
#
-
# If the second parameter is left off, then Dependencies will construct a
-
# set of names that the file at +path+ may define. See
-
# +loadable_constants_for_path+ for more details.
-
1
def load_file(path, const_paths = loadable_constants_for_path(path))
-
361
log_call path, const_paths
-
361
const_paths = [const_paths].compact unless const_paths.is_a? Array
-
361
parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || :Object }
-
-
361
result = nil
-
361
newly_defined_paths = new_constants_in(*parent_paths) do
-
361
result = Kernel.load path
-
end
-
-
10
autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
-
10
autoloaded_constants.uniq!
-
10
log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
-
10
result
-
end
-
-
# Returns the constant path for the provided parent and constant name.
-
1
def qualified_name_for(mod, name)
-
613
mod_name = to_constant_name mod
-
613
mod_name == "Object" ? name.to_s : "#{mod_name}::#{name}"
-
end
-
-
# Load the constant named +const_name+ which is missing from +from_mod+. If
-
# it is not possible to load the constant into from_mod, try its parent
-
# module using +const_missing+.
-
1
def load_missing_constant(from_mod, const_name)
-
591
log_call from_mod, const_name
-
-
591
unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod)
-
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
-
end
-
-
591
raise NameError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name, false)
-
-
591
qualified_name = qualified_name_for from_mod, const_name
-
591
path_suffix = qualified_name.underscore
-
-
591
file_path = search_for_file(path_suffix)
-
-
591
if file_path
-
expanded = File.expand_path(file_path)
-
expanded.sub!(/\.rb\z/, '')
-
-
if loaded.include?(expanded)
-
raise "Circular dependency detected while autoloading constant #{qualified_name}"
-
else
-
require_or_load(expanded)
-
raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false)
-
return from_mod.const_get(const_name)
-
end
-
elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
-
return mod
-
591
elsif (parent = from_mod.parent) && parent != from_mod &&
-
79
! from_mod.parents.any? { |p| p.const_defined?(const_name, false) }
-
# If our parents do not have a constant named +const_name+ then we are free
-
# to attempt to load upwards. If they do have such a constant, then this
-
# const_missing must be due to from_mod::const_name, which should not
-
# return constants from from_mod's parents.
-
22
begin
-
# Since Ruby does not pass the nesting at the point the unknown
-
# constant triggered the callback we cannot fully emulate constant
-
# name lookup and need to make a trade-off: we are going to assume
-
# that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even
-
# though it might not be. Counterexamples are
-
#
-
# class Foo::Bar
-
# Module.nesting # => [Foo::Bar]
-
# end
-
#
-
# or
-
#
-
# module M::N
-
# module S::T
-
# Module.nesting # => [S::T, M::N]
-
# end
-
# end
-
#
-
# for example.
-
22
return parent.const_missing(const_name)
-
rescue NameError => e
-
22
raise unless e.missing_name? qualified_name_for(parent, const_name)
-
end
-
end
-
-
591
raise NameError,
-
"uninitialized constant #{qualified_name}",
-
17173
caller.reject { |l| l.starts_with? __FILE__ }
-
end
-
-
# Remove the constants that have been autoloaded, and those that have been
-
# marked for unloading. Before each constant is removed a callback is sent
-
# to its class/module if it implements +before_remove_const+.
-
#
-
# The callback implementation should be restricted to cleaning up caches, etc.
-
# as the environment will be in an inconsistent state, e.g. other constants
-
# may have already been unloaded and not accessible.
-
1
def remove_unloadable_constants!
-
7
autoloaded_constants.each { |const| remove_constant const }
-
7
autoloaded_constants.clear
-
7
Reference.clear!
-
7
explicitly_unloadable_constants.each { |const| remove_constant const }
-
end
-
-
1
class ClassCache
-
1
def initialize
-
1
@store = Hash.new
-
end
-
-
1
def empty?
-
@store.empty?
-
end
-
-
1
def key?(key)
-
@store.key?(key)
-
end
-
-
1
def get(key)
-
3309
key = key.name if key.respond_to?(:name)
-
3309
@store[key] ||= Inflector.constantize(key)
-
end
-
1
alias :[] :get
-
-
1
def safe_get(key)
-
key = key.name if key.respond_to?(:name)
-
@store[key] ||= Inflector.safe_constantize(key)
-
end
-
-
1
def store(klass)
-
751
return self unless klass.respond_to?(:name)
-
raise(ArgumentError, 'anonymous classes cannot be cached') if klass.name.empty?
-
@store[klass.name] = klass
-
self
-
end
-
-
1
def clear!
-
7
@store.clear
-
end
-
end
-
-
1
Reference = ClassCache.new
-
-
# Store a reference to a class +klass+.
-
1
def reference(klass)
-
751
Reference.store klass
-
end
-
-
# Get the reference for class named +name+.
-
# Raises an exception if referenced class does not exist.
-
1
def constantize(name)
-
Reference.get(name)
-
end
-
-
# Get the reference for class named +name+ if one exists.
-
# Otherwise returns +nil+.
-
1
def safe_constantize(name)
-
Reference.safe_get(name)
-
end
-
-
# Determine if the given constant has been automatically loaded.
-
1
def autoloaded?(desc)
-
# No name => anonymous module.
-
return false if desc.is_a?(Module) && desc.anonymous?
-
name = to_constant_name desc
-
return false unless qualified_const_defined? name
-
return autoloaded_constants.include?(name)
-
end
-
-
# Will the provided constant descriptor be unloaded?
-
1
def will_unload?(const_desc)
-
autoloaded?(const_desc) ||
-
explicitly_unloadable_constants.include?(to_constant_name(const_desc))
-
end
-
-
# Mark the provided constant name for unloading. This constant will be
-
# unloaded on each request, not just the next one.
-
1
def mark_for_unload(const_desc)
-
name = to_constant_name const_desc
-
if explicitly_unloadable_constants.include? name
-
false
-
else
-
explicitly_unloadable_constants << name
-
true
-
end
-
end
-
-
# Run the provided block and detect the new constants that were loaded during
-
# its execution. Constants may only be regarded as 'new' once -- so if the
-
# block calls +new_constants_in+ again, then the constants defined within the
-
# inner call will not be reported in this one.
-
#
-
# If the provided block does not run to completion, and instead raises an
-
# exception, any new constants are regarded as being only partially defined
-
# and will be removed immediately.
-
1
def new_constants_in(*descs)
-
361
log_call(*descs)
-
-
361
constant_watch_stack.watch_namespaces(descs)
-
361
aborting = true
-
-
361
begin
-
361
yield # Now yield to the code that is to define new constants.
-
10
aborting = false
-
ensure
-
361
new_constants = constant_watch_stack.new_constants
-
-
361
log "New constants: #{new_constants * ', '}"
-
361
return new_constants unless aborting
-
-
351
log "Error during loading, removing partially loaded constants "
-
351
new_constants.each { |c| remove_constant(c) }.clear
-
end
-
-
[]
-
end
-
-
# Convert the provided const desc to a qualified constant name (as a string).
-
# A module, class, symbol, or string may be provided.
-
1
def to_constant_name(desc) #:nodoc:
-
613
case desc
-
when String then desc.sub(/^::/, '')
-
when Symbol then desc.to_s
-
when Module
-
desc.name.presence ||
-
613
raise(ArgumentError, "Anonymous modules have no name to be referenced by")
-
else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
-
end
-
end
-
-
1
def remove_constant(const) #:nodoc:
-
return false unless qualified_const_defined? const
-
-
# Normalize ::Foo, Foo, Object::Foo, and ::Object::Foo to Object::Foo
-
names = const.to_s.sub(/^::(Object)?/, 'Object::').split("::")
-
to_remove = names.pop
-
parent = Inflector.constantize(names * '::')
-
-
log "removing constant #{const}"
-
constantized = constantize(const)
-
constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
-
parent.instance_eval { remove_const to_remove }
-
-
true
-
end
-
-
1
protected
-
1
def log_call(*args)
-
1689
if log_activity?
-
arg_str = args.collect { |arg| arg.inspect } * ', '
-
/in `([a-z_\?\!]+)'/ =~ caller(1).first
-
selector = $1 || '<unknown>'
-
log "called #{selector}(#{arg_str})"
-
end
-
end
-
-
1
def log(msg)
-
1073
logger.debug "Dependencies: #{msg}" if log_activity?
-
end
-
-
1
def log_activity?
-
2762
logger && log_activity
-
end
-
end
-
end
-
-
1
ActiveSupport::Dependencies.hook!
-
1
require "active_support/inflector/methods"
-
-
1
module ActiveSupport
-
# Autoload and eager load conveniences for your library.
-
#
-
# This module allows you to define autoloads based on
-
# Rails conventions (i.e. no need to define the path
-
# it is automatically guessed based on the filename)
-
# and also define a set of constants that needs to be
-
# eager loaded:
-
#
-
# module MyLib
-
# extend ActiveSupport::Autoload
-
#
-
# autoload :Model
-
#
-
# eager_autoload do
-
# autoload :Cache
-
# end
-
# end
-
#
-
# Then your library can be eager loaded by simply calling:
-
#
-
# MyLib.eager_load!
-
1
module Autoload
-
1
def self.extended(base) # :nodoc:
-
22
base.class_eval do
-
22
@_autoloads = {}
-
22
@_under_path = nil
-
22
@_at_path = nil
-
22
@_eager_autoload = false
-
end
-
end
-
-
1
def autoload(const_name, path = @_at_path)
-
350
unless path
-
267
full = [name, @_under_path, const_name.to_s, path].compact.join("::")
-
267
path = Inflector.underscore(full)
-
end
-
-
350
if @_eager_autoload
-
113
@_autoloads[const_name] = path
-
end
-
-
350
super const_name, path
-
end
-
-
1
def autoload_under(path)
-
7
@_under_path, old_path = path, @_under_path
-
7
yield
-
ensure
-
7
@_under_path = old_path
-
end
-
-
1
def autoload_at(path)
-
7
@_at_path, old_path = path, @_at_path
-
7
yield
-
ensure
-
7
@_at_path = old_path
-
end
-
-
1
def eager_autoload
-
16
old_eager, @_eager_autoload = @_eager_autoload, true
-
16
yield
-
ensure
-
16
@_eager_autoload = old_eager
-
end
-
-
1
def eager_load!
-
@_autoloads.values.each { |file| require file }
-
end
-
-
1
def autoloads
-
@_autoloads
-
end
-
end
-
end
-
1
require 'singleton'
-
-
1
module ActiveSupport
-
# \Deprecation specifies the API used by Rails to deprecate methods, instance
-
# variables, objects and constants.
-
1
class Deprecation
-
# active_support.rb sets an autoload for ActiveSupport::Deprecation.
-
#
-
# If these requires were at the top of the file the constant would not be
-
# defined by the time their files were loaded. Since some of them reopen
-
# ActiveSupport::Deprecation its autoload would be triggered, resulting in
-
# a circular require warning for active_support/deprecation.rb.
-
#
-
# So, we define the constant first, and load dependencies later.
-
1
require 'active_support/deprecation/instance_delegator'
-
1
require 'active_support/deprecation/behaviors'
-
1
require 'active_support/deprecation/reporting'
-
1
require 'active_support/deprecation/method_wrappers'
-
1
require 'active_support/deprecation/proxy_wrappers'
-
1
require 'active_support/core_ext/module/deprecation'
-
-
1
include Singleton
-
1
include InstanceDelegator
-
1
include Behavior
-
1
include Reporting
-
1
include MethodWrapper
-
-
# The version the deprecated behavior will be removed, by default.
-
1
attr_accessor :deprecation_horizon
-
-
# It accepts two parameters on initialization. The first is an version of library
-
# and the second is an library name
-
#
-
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
-
1
def initialize(deprecation_horizon = '4.1', gem_name = 'Rails')
-
1
self.gem_name = gem_name
-
1
self.deprecation_horizon = deprecation_horizon
-
# By default, warnings are not silenced and debugging is off.
-
1
self.silenced = false
-
1
self.debug = false
-
end
-
end
-
end
-
1
require "active_support/notifications"
-
-
1
module ActiveSupport
-
1
class Deprecation
-
# Default warning behaviors per Rails.env.
-
1
DEFAULT_BEHAVIORS = {
-
:stderr => Proc.new { |message, callstack|
-
$stderr.puts(message)
-
$stderr.puts callstack.join("\n ") if debug
-
},
-
:log => Proc.new { |message, callstack|
-
logger =
-
if defined?(Rails) && Rails.logger
-
Rails.logger
-
else
-
require 'active_support/logger'
-
ActiveSupport::Logger.new($stderr)
-
end
-
logger.warn message
-
logger.debug callstack.join("\n ") if debug
-
},
-
:notify => Proc.new { |message, callstack|
-
ActiveSupport::Notifications.instrument("deprecation.rails",
-
:message => message, :callstack => callstack)
-
},
-
:silence => Proc.new { |message, callstack| }
-
}
-
-
1
module Behavior
-
# Whether to print a backtrace along with the warning.
-
1
attr_accessor :debug
-
-
# Returns the current behavior or if one isn't set, defaults to +:stderr+.
-
1
def behavior
-
101
@behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
-
end
-
-
# Sets the behavior to the specified value. Can be a single value, array,
-
# or an object that responds to +call+.
-
#
-
# Available behaviors:
-
#
-
# [+stderr+] Log all deprecation warnings to +$stderr+.
-
# [+log+] Log all deprecation warnings to +Rails.logger+.
-
# [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
-
# [+silence+] Do nothing.
-
#
-
# Setting behaviors only affects deprecations that happen after boot time.
-
# Deprecation warnings raised by gems are not affected by this setting
-
# because they happen before Rails boots up.
-
#
-
# ActiveSupport::Deprecation.behavior = :stderr
-
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
-
# ActiveSupport::Deprecation.behavior = MyCustomHandler
-
# ActiveSupport::Deprecation.behavior = proc { |message, callstack|
-
# # custom stuff
-
# }
-
1
def behavior=(behavior)
-
124
@behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/module/delegation'
-
-
1
module ActiveSupport
-
1
class Deprecation
-
1
module InstanceDelegator
-
1
def self.included(base)
-
1
base.extend(ClassMethods)
-
1
base.public_class_method :new
-
end
-
-
1
module ClassMethods
-
1
def include(included_module)
-
15
included_module.instance_methods.each { |m| method_added(m) }
-
3
super
-
end
-
-
1
def method_added(method_name)
-
15
singleton_class.delegate(method_name, to: :instance)
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/aliasing'
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
module ActiveSupport
-
1
class Deprecation
-
1
module MethodWrapper
-
# Declare that a method has been deprecated.
-
#
-
# module Fred
-
# extend self
-
#
-
# def foo; end
-
# def bar; end
-
# def baz; end
-
# end
-
#
-
# ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead')
-
# # => [:foo, :bar, :baz]
-
#
-
# Fred.foo
-
# # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1."
-
#
-
# Fred.bar
-
# # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)."
-
#
-
# Fred.baz
-
# # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)."
-
1
def deprecate_methods(target_module, *method_names)
-
3
options = method_names.extract_options!
-
3
deprecator = options.delete(:deprecator) || ActiveSupport::Deprecation.instance
-
3
method_names += options.keys
-
-
3
method_names.each do |method_name|
-
5
target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation|
-
5
target_module.send(:define_method, "#{target}_with_deprecation#{punctuation}") do |*args, &block|
-
deprecator.deprecation_warning(method_name, options[method_name])
-
send(:"#{target}_without_deprecation#{punctuation}", *args, &block)
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/inflector/methods'
-
-
1
module ActiveSupport
-
1
class Deprecation
-
1
class DeprecationProxy #:nodoc:
-
1
def self.new(*args, &block)
-
2
object = args.first
-
-
2
return object unless object
-
2
super
-
end
-
-
107
instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$/ }
-
-
# Don't give a deprecation warning on inspect since test/unit and error
-
# logs rely on it for diagnostics.
-
1
def inspect
-
target.inspect
-
end
-
-
1
private
-
1
def method_missing(called, *args, &block)
-
warn caller, called, args
-
target.__send__(called, *args, &block)
-
end
-
end
-
-
# This DeprecatedObjectProxy transforms object to depracated object.
-
#
-
# @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!")
-
# @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance)
-
#
-
# When someone execute any method expect +inspect+ on proxy object this will
-
# trigger +warn+ method on +deprecator_instance+.
-
#
-
# Default deprecator is <tt>ActiveSupport::Deprecation</tt>
-
1
class DeprecatedObjectProxy < DeprecationProxy
-
1
def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
-
@object = object
-
@message = message
-
@deprecator = deprecator
-
end
-
-
1
private
-
1
def target
-
@object
-
end
-
-
1
def warn(callstack, called, args)
-
@deprecator.warn(@message, callstack)
-
end
-
end
-
-
# This DeprecatedInstanceVariableProxy transforms instance variable to
-
# depracated instance variable.
-
#
-
# class Example
-
# def initialize(deprecator)
-
# @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
-
# @_request = :a_request
-
# end
-
#
-
# def request
-
# @_request
-
# end
-
#
-
# def old_request
-
# @request
-
# end
-
# end
-
#
-
# When someone execute any method on @request variable this will trigger
-
# +warn+ method on +deprecator_instance+ and will fetch <tt>@_request</tt>
-
# variable via +request+ method and execute the same method on non-proxy
-
# instance variable.
-
#
-
# Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
-
1
class DeprecatedInstanceVariableProxy < DeprecationProxy
-
1
def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance)
-
@instance = instance
-
@method = method
-
@var = var
-
@deprecator = deprecator
-
end
-
-
1
private
-
1
def target
-
@instance.__send__(@method)
-
end
-
-
1
def warn(callstack, called, args)
-
@deprecator.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
-
end
-
end
-
-
# This DeprecatedConstantProxy transforms constant to depracated constant.
-
#
-
# OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST')
-
# OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance)
-
#
-
# When someone use old constant this will trigger +warn+ method on
-
# +deprecator_instance+.
-
#
-
# Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
-
1
class DeprecatedConstantProxy < DeprecationProxy
-
1
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
-
2
@old_const = old_const
-
2
@new_const = new_const
-
2
@deprecator = deprecator
-
end
-
-
1
def class
-
target.class
-
end
-
-
1
private
-
1
def target
-
ActiveSupport::Inflector.constantize(@new_const.to_s)
-
end
-
-
1
def warn(callstack, called, args)
-
@deprecator.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
class Deprecation
-
1
module Reporting
-
# Whether to print a message (silent mode)
-
1
attr_accessor :silenced
-
# Name of gem where method is deprecated
-
1
attr_accessor :gem_name
-
-
# Outputs a deprecation warning to the output configured by
-
# <tt>ActiveSupport::Deprecation.behavior</tt>.
-
#
-
# ActiveSupport::Deprecation.warn('something broke!')
-
# # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
-
1
def warn(message = nil, callstack = nil)
-
71
return if silenced
-
-
70
callstack ||= caller(2)
-
70
deprecation_message(callstack, message).tap do |m|
-
140
behavior.each { |b| b.call(m, callstack) }
-
end
-
end
-
-
# Silence deprecation warnings within the block.
-
#
-
# ActiveSupport::Deprecation.warn('something broke!')
-
# # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
-
#
-
# ActiveSupport::Deprecation.silence do
-
# ActiveSupport::Deprecation.warn('something broke!')
-
# end
-
# # => nil
-
1
def silence
-
8
old_silenced, @silenced = @silenced, true
-
8
yield
-
ensure
-
8
@silenced = old_silenced
-
end
-
-
1
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
-
1
caller_backtrace ||= caller(2)
-
1
deprecated_method_warning(deprecated_method_name, message).tap do |msg|
-
1
warn(msg, caller_backtrace)
-
end
-
end
-
-
1
private
-
# Outputs a deprecation warning message
-
#
-
# ActiveSupport::Deprecation.deprecated_method_warning(:method_name)
-
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}"
-
# ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method)
-
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)"
-
# ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message")
-
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)"
-
1
def deprecated_method_warning(method_name, message = nil)
-
1
warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}"
-
1
case message
-
1
when Symbol then "#{warning} (use #{message} instead)"
-
when String then "#{warning} (#{message})"
-
else warning
-
end
-
end
-
-
1
def deprecation_message(callstack, message = nil)
-
70
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
-
70
message += '.' unless message =~ /\.$/
-
70
"DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
-
end
-
-
1
def deprecation_caller_message(callstack)
-
70
file, line, method = extract_callstack(callstack)
-
70
if file
-
70
if line && method
-
70
"(called from #{method} at #{file}:#{line})"
-
else
-
"(called from #{file}:#{line})"
-
end
-
end
-
end
-
-
1
def extract_callstack(callstack)
-
70
rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
-
603
offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first
-
70
if offending_line
-
70
if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
-
70
md.captures
-
else
-
offending_line
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
# This module provides an internal implementation to track descendants
-
# which is faster than iterating through ObjectSpace.
-
1
module DescendantsTracker
-
1
@@direct_descendants = {}
-
-
1
class << self
-
1
def direct_descendants(klass)
-
@@direct_descendants[klass] || []
-
end
-
-
1
def descendants(klass)
-
195
arr = []
-
195
accumulate_descendants(klass, arr)
-
195
arr
-
end
-
-
1
def clear
-
if defined? ActiveSupport::Dependencies
-
@@direct_descendants.each do |klass, descendants|
-
if ActiveSupport::Dependencies.autoloaded?(klass)
-
@@direct_descendants.delete(klass)
-
else
-
descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
-
end
-
end
-
else
-
@@direct_descendants.clear
-
end
-
end
-
-
# This is the only method that is not thread safe, but is only ever called
-
# during the eager loading phase.
-
1
def store_inherited(klass, descendant)
-
761
(@@direct_descendants[klass] ||= []) << descendant
-
end
-
-
1
private
-
1
def accumulate_descendants(klass, acc)
-
195
if direct_descendants = @@direct_descendants[klass]
-
acc.concat(direct_descendants)
-
direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) }
-
end
-
end
-
end
-
-
1
def inherited(base)
-
761
DescendantsTracker.store_inherited(self, base)
-
761
super
-
end
-
-
1
def direct_descendants
-
DescendantsTracker.direct_descendants(self)
-
end
-
-
1
def descendants
-
DescendantsTracker.descendants(self)
-
end
-
end
-
end
-
1
require 'active_support/basic_object'
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/object/acts_like'
-
-
1
module ActiveSupport
-
# Provides accurate date and time measurements using Date#advance and
-
# Time#advance, respectively. It mainly supports the methods on Numeric.
-
#
-
# 1.month.ago # equivalent to Time.now.advance(months: -1)
-
1
class Duration < BasicObject
-
1
attr_accessor :value, :parts
-
-
1
def initialize(value, parts) #:nodoc:
-
1037
@value, @parts = value, parts
-
end
-
-
# Adds another Duration or a Numeric to this Duration. Numeric values
-
# are treated as seconds.
-
1
def +(other)
-
30
if Duration === other
-
6
Duration.new(value + other.value, @parts + other.parts)
-
else
-
24
Duration.new(value + other, @parts + [[:seconds, other]])
-
end
-
end
-
-
# Subtracts another Duration or a Numeric from this Duration. Numeric
-
# values are treated as seconds.
-
1
def -(other)
-
24
self + (-other)
-
end
-
-
1
def -@ #:nodoc:
-
Duration.new(-value, parts.map { |type,number| [type, -number] })
-
end
-
-
1
def is_a?(klass) #:nodoc:
-
1016
Duration == klass || value.is_a?(klass)
-
end
-
1
alias :kind_of? :is_a?
-
-
# Returns +true+ if +other+ is also a Duration instance with the
-
# same +value+, or if <tt>other == value</tt>.
-
1
def ==(other)
-
if Duration === other
-
other.value == value
-
else
-
other == value
-
end
-
end
-
-
1
def self.===(other) #:nodoc:
-
7862
other.is_a?(Duration)
-
rescue ::NoMethodError
-
false
-
end
-
-
# Calculates a new Time or Date that is as far in the future
-
# as this Duration represents.
-
1
def since(time = ::Time.current)
-
515
sum(1, time)
-
end
-
1
alias :from_now :since
-
-
# Calculates a new Time or Date that is as far in the past
-
# as this Duration represents.
-
1
def ago(time = ::Time.current)
-
54
sum(-1, time)
-
end
-
1
alias :until :ago
-
-
1
def inspect #:nodoc:
-
consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
-
parts = [:years, :months, :days, :minutes, :seconds].map do |length|
-
n = consolidated[length]
-
"#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
-
end.compact
-
parts = ["0 seconds"] if parts.empty?
-
parts.to_sentence(:locale => :en)
-
end
-
-
1
def as_json(options = nil) #:nodoc:
-
to_i
-
end
-
-
1
protected
-
-
1
def sum(sign, time = ::Time.current) #:nodoc:
-
569
parts.inject(time) do |t,(type,number)|
-
571
if t.acts_like?(:time) || t.acts_like?(:date)
-
571
if type == :seconds
-
318
t.since(sign * number)
-
else
-
253
t.advance(type => sign * number)
-
end
-
else
-
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
-
end
-
end
-
end
-
-
1
private
-
-
1
def method_missing(method, *args, &block) #:nodoc:
-
572
value.send(method, *args, &block)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/keys'
-
-
1
module ActiveSupport
-
# Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
-
# to be the same.
-
#
-
# rgb = ActiveSupport::HashWithIndifferentAccess.new
-
#
-
# rgb[:black] = '#000000'
-
# rgb[:black] # => '#000000'
-
# rgb['black'] # => '#000000'
-
#
-
# rgb['white'] = '#FFFFFF'
-
# rgb[:white] # => '#FFFFFF'
-
# rgb['white'] # => '#FFFFFF'
-
#
-
# Internally symbols are mapped to strings when used as keys in the entire
-
# writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This
-
# mapping belongs to the public interface. For example, given:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
-
#
-
# You are guaranteed that the key is returned as a string:
-
#
-
# hash.keys # => ["a"]
-
#
-
# Technically other types of keys are accepted:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
-
# hash[0] = 0
-
# hash # => {"a"=>1, 0=>0}
-
#
-
# but this class is intended for use cases where strings or symbols are the
-
# expected keys and it is convenient to understand both as the same. For
-
# example the +params+ hash in Ruby on Rails.
-
#
-
# Note that core extensions define <tt>Hash#with_indifferent_access</tt>:
-
#
-
# rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access
-
#
-
# which may be handy.
-
1
class HashWithIndifferentAccess < Hash
-
# Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
-
# this class.
-
1
def extractable_options?
-
true
-
end
-
-
1
def with_indifferent_access
-
1559
dup
-
end
-
-
1
def nested_under_indifferent_access
-
183
self
-
end
-
-
1
def initialize(constructor = {})
-
14611
if constructor.is_a?(Hash)
-
14569
super()
-
14569
update(constructor)
-
else
-
42
super(constructor)
-
end
-
end
-
-
1
def default(key = nil)
-
12486
if key.is_a?(Symbol) && include?(key = key.to_s)
-
2264
self[key]
-
else
-
10222
super
-
end
-
end
-
-
1
def self.new_from_hash_copying_default(hash)
-
4802
new(hash).tap do |new_hash|
-
4802
new_hash.default = hash.default
-
end
-
end
-
-
1
def self.[](*args)
-
51
new.merge(Hash[*args])
-
end
-
-
1
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
-
1
alias_method :regular_update, :update unless method_defined?(:regular_update)
-
-
# Assigns a new value to the hash:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash[:key] = 'value'
-
#
-
# This value can be later fetched using either +:key+ or +'key'+.
-
1
def []=(key, value)
-
258
regular_writer(convert_key(key), convert_value(value))
-
end
-
-
1
alias_method :store, :[]=
-
-
# Updates the receiver in-place, merging in the hash passed as argument:
-
#
-
# hash_1 = ActiveSupport::HashWithIndifferentAccess.new
-
# hash_1[:key] = 'value'
-
#
-
# hash_2 = ActiveSupport::HashWithIndifferentAccess.new
-
# hash_2[:key] = 'New Value!'
-
#
-
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
-
#
-
# The argument can be either an
-
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> or a regular +Hash+.
-
# In either case the merge respects the semantics of indifferent access.
-
#
-
# If the argument is a regular hash with keys +:key+ and +"key"+ only one
-
# of the values end up in the receiver, but which one is unspecified.
-
#
-
# When given a block, the value for duplicated keys will be determined
-
# by the result of invoking the block with the duplicated key, the value
-
# in the receiver, and the value in +other_hash+. The rules for duplicated
-
# keys follow the semantics of indifferent access:
-
#
-
# hash_1[:key] = 10
-
# hash_2['key'] = 12
-
# hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22}
-
1
def update(other_hash)
-
17857
if other_hash.is_a? HashWithIndifferentAccess
-
9281
super(other_hash)
-
else
-
8576
other_hash.each_pair do |key, value|
-
9396
if block_given? && key?(key)
-
value = yield(convert_key(key), self[key], value)
-
end
-
9396
regular_writer(convert_key(key), convert_value(value))
-
end
-
8576
self
-
end
-
end
-
-
1
alias_method :merge!, :update
-
-
# Checks the hash for a key matching the argument passed in:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash['key'] = 'value'
-
# hash.key?(:key) # => true
-
# hash.key?('key') # => true
-
1
def key?(key)
-
3918
super(convert_key(key))
-
end
-
-
1
alias_method :include?, :key?
-
1
alias_method :has_key?, :key?
-
1
alias_method :member?, :key?
-
-
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
-
# either a string or a symbol:
-
#
-
# counters = ActiveSupport::HashWithIndifferentAccess.new
-
# counters[:foo] = 1
-
#
-
# counters.fetch('foo') # => 1
-
# counters.fetch(:bar, 0) # => 0
-
# counters.fetch(:bar) {|key| 0} # => 0
-
# counters.fetch(:zoo) # => KeyError: key not found: "zoo"
-
1
def fetch(key, *extras)
-
5
super(convert_key(key), *extras)
-
end
-
-
# Returns an array of the values at the specified indices:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash[:a] = 'x'
-
# hash[:b] = 'y'
-
# hash.values_at('a', 'b') # => ["x", "y"]
-
1
def values_at(*indices)
-
4
indices.collect {|key| self[convert_key(key)]}
-
end
-
-
# Returns an exact copy of the hash.
-
1
def dup
-
8603
self.class.new(self).tap do |new_hash|
-
8603
new_hash.default = default
-
end
-
end
-
-
# This method has the same semantics of +update+, except it does not
-
# modify the receiver but rather returns a new hash with indifferent
-
# access with the result of the merge.
-
1
def merge(hash, &block)
-
1613
self.dup.update(hash, &block)
-
end
-
-
# Like +merge+ but the other way around: Merges the receiver into the
-
# argument and returns a new hash with indifferent access as result:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash['a'] = nil
-
# hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
-
1
def reverse_merge(other_hash)
-
super(self.class.new_from_hash_copying_default(other_hash))
-
end
-
-
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
-
1
def reverse_merge!(other_hash)
-
replace(reverse_merge( other_hash ))
-
end
-
-
# Replaces the contents of this hash with other_hash.
-
#
-
# h = { "a" => 100, "b" => 200 }
-
# h.replace({ "c" => 300, "d" => 400 }) #=> {"c"=>300, "d"=>400}
-
1
def replace(other_hash)
-
super(self.class.new_from_hash_copying_default(other_hash))
-
end
-
-
# Removes the specified key from the hash.
-
1
def delete(key)
-
11221
super(convert_key(key))
-
end
-
-
1
def stringify_keys!; self end
-
1
def deep_stringify_keys!; self end
-
2121
def stringify_keys; dup end
-
1
def deep_stringify_keys; dup end
-
1
undef :symbolize_keys!
-
1
undef :deep_symbolize_keys!
-
4
def symbolize_keys; to_hash.symbolize_keys end
-
1
def deep_symbolize_keys; to_hash.deep_symbolize_keys end
-
1
def to_options!; self end
-
-
# Convert to a regular hash with string keys.
-
1
def to_hash
-
3
Hash.new(default).merge!(self)
-
end
-
-
1
protected
-
1
def convert_key(key)
-
24869
key.kind_of?(Symbol) ? key.to_s : key
-
end
-
-
1
def convert_value(value)
-
9712
if value.is_a? Hash
-
262
value.nested_under_indifferent_access
-
9450
elsif value.is_a?(Array)
-
40
value = value.dup if value.frozen?
-
98
value.map! { |e| convert_value(e) }
-
else
-
9410
value
-
end
-
end
-
end
-
end
-
-
1
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
-
1
begin
-
1
require 'i18n'
-
1
require 'active_support/lazy_load_hooks'
-
rescue LoadError => e
-
$stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install"
-
raise e
-
end
-
-
1
ActiveSupport.run_load_hooks(:i18n)
-
1
I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
-
1
require 'active_support/inflector/inflections'
-
-
1
module ActiveSupport
-
1
Inflector.inflections(:en) do |inflect|
-
1
inflect.plural(/$/, 's')
-
1
inflect.plural(/s$/i, 's')
-
1
inflect.plural(/^(ax|test)is$/i, '\1es')
-
1
inflect.plural(/(octop|vir)us$/i, '\1i')
-
1
inflect.plural(/(octop|vir)i$/i, '\1i')
-
1
inflect.plural(/(alias|status)$/i, '\1es')
-
1
inflect.plural(/(bu)s$/i, '\1ses')
-
1
inflect.plural(/(buffal|tomat)o$/i, '\1oes')
-
1
inflect.plural(/([ti])um$/i, '\1a')
-
1
inflect.plural(/([ti])a$/i, '\1a')
-
1
inflect.plural(/sis$/i, 'ses')
-
1
inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
-
1
inflect.plural(/(hive)$/i, '\1s')
-
1
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
-
1
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
-
1
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
-
1
inflect.plural(/^(m|l)ouse$/i, '\1ice')
-
1
inflect.plural(/^(m|l)ice$/i, '\1ice')
-
1
inflect.plural(/^(ox)$/i, '\1en')
-
1
inflect.plural(/^(oxen)$/i, '\1')
-
1
inflect.plural(/(quiz)$/i, '\1zes')
-
-
1
inflect.singular(/s$/i, '')
-
1
inflect.singular(/(ss)$/i, '\1')
-
1
inflect.singular(/(n)ews$/i, '\1ews')
-
1
inflect.singular(/([ti])a$/i, '\1um')
-
1
inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
-
1
inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
-
1
inflect.singular(/([^f])ves$/i, '\1fe')
-
1
inflect.singular(/(hive)s$/i, '\1')
-
1
inflect.singular(/(tive)s$/i, '\1')
-
1
inflect.singular(/([lr])ves$/i, '\1f')
-
1
inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
-
1
inflect.singular(/(s)eries$/i, '\1eries')
-
1
inflect.singular(/(m)ovies$/i, '\1ovie')
-
1
inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
-
1
inflect.singular(/^(m|l)ice$/i, '\1ouse')
-
1
inflect.singular(/(bus)(es)?$/i, '\1')
-
1
inflect.singular(/(o)es$/i, '\1')
-
1
inflect.singular(/(shoe)s$/i, '\1')
-
1
inflect.singular(/(cris|test)(is|es)$/i, '\1is')
-
1
inflect.singular(/^(a)x[ie]s$/i, '\1xis')
-
1
inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
-
1
inflect.singular(/(alias|status)(es)?$/i, '\1')
-
1
inflect.singular(/^(ox)en/i, '\1')
-
1
inflect.singular(/(vert|ind)ices$/i, '\1ex')
-
1
inflect.singular(/(matr)ices$/i, '\1ix')
-
1
inflect.singular(/(quiz)zes$/i, '\1')
-
1
inflect.singular(/(database)s$/i, '\1')
-
-
1
inflect.irregular('person', 'people')
-
1
inflect.irregular('man', 'men')
-
1
inflect.irregular('child', 'children')
-
1
inflect.irregular('sex', 'sexes')
-
1
inflect.irregular('move', 'moves')
-
1
inflect.irregular('cow', 'kine')
-
1
inflect.irregular('zombie', 'zombies')
-
-
1
inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
-
end
-
end
-
# in case active_support/inflector is required without the rest of active_support
-
1
require 'active_support/inflector/inflections'
-
1
require 'active_support/inflector/transliterate'
-
1
require 'active_support/inflector/methods'
-
-
1
require 'active_support/inflections'
-
1
require 'active_support/core_ext/string/inflections'
-
1
require 'active_support/core_ext/array/prepend_and_append'
-
1
require 'active_support/i18n'
-
-
1
module ActiveSupport
-
1
module Inflector
-
1
extend self
-
-
# A singleton instance of this class is yielded by Inflector.inflections,
-
# which can then be used to specify additional inflection rules. If passed
-
# an optional locale, rules for other languages can be specified. The
-
# default locale is <tt>:en</tt>. Only rules for English are provided.
-
#
-
# ActiveSupport::Inflector.inflections(:en) do |inflect|
-
# inflect.plural /^(ox)$/i, '\1\2en'
-
# inflect.singular /^(ox)en/i, '\1'
-
#
-
# inflect.irregular 'octopus', 'octopi'
-
#
-
# inflect.uncountable 'equipment'
-
# end
-
#
-
# New rules are added at the top. So in the example above, the irregular
-
# rule for octopus will now be the first of the pluralization and
-
# singularization rules that is runs. This guarantees that your rules run
-
# before any of the rules that may already have been loaded.
-
1
class Inflections
-
1
def self.instance(locale = :en)
-
11516
@__instance__ ||= Hash.new { |h, k| h[k] = new }
-
11515
@__instance__[locale]
-
end
-
-
1
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
-
-
1
def initialize
-
1
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
-
end
-
-
# Private, for the test suite.
-
1
def initialize_dup(orig) # :nodoc:
-
%w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
-
instance_variable_set("@#{scope}", orig.send(scope).dup)
-
end
-
end
-
-
# Specifies a new acronym. An acronym must be specified as it will appear
-
# in a camelized string. An underscore string that contains the acronym
-
# will retain the acronym when passed to +camelize+, +humanize+, or
-
# +titleize+. A camelized string that contains the acronym will maintain
-
# the acronym when titleized or humanized, and will convert the acronym
-
# into a non-delimited single lowercase word when passed to +underscore+.
-
#
-
# acronym 'HTML'
-
# titleize 'html' #=> 'HTML'
-
# camelize 'html' #=> 'HTML'
-
# underscore 'MyHTML' #=> 'my_html'
-
#
-
# The acronym, however, must occur as a delimited unit and not be part of
-
# another word for conversions to recognize it:
-
#
-
# acronym 'HTTP'
-
# camelize 'my_http_delimited' #=> 'MyHTTPDelimited'
-
# camelize 'https' #=> 'Https', not 'HTTPs'
-
# underscore 'HTTPS' #=> 'http_s', not 'https'
-
#
-
# acronym 'HTTPS'
-
# camelize 'https' #=> 'HTTPS'
-
# underscore 'HTTPS' #=> 'https'
-
#
-
# Note: Acronyms that are passed to +pluralize+ will no longer be
-
# recognized, since the acronym will not occur as a delimited unit in the
-
# pluralized result. To work around this, you must specify the pluralized
-
# form as an acronym as well:
-
#
-
# acronym 'API'
-
# camelize(pluralize('api')) #=> 'Apis'
-
#
-
# acronym 'APIs'
-
# camelize(pluralize('api')) #=> 'APIs'
-
#
-
# +acronym+ may be used to specify any word that contains an acronym or
-
# otherwise needs to maintain a non-standard capitalization. The only
-
# restriction is that the word must begin with a capital letter.
-
#
-
# acronym 'RESTful'
-
# underscore 'RESTful' #=> 'restful'
-
# underscore 'RESTfulController' #=> 'restful_controller'
-
# titleize 'RESTfulController' #=> 'RESTful Controller'
-
# camelize 'restful' #=> 'RESTful'
-
# camelize 'restful_controller' #=> 'RESTfulController'
-
#
-
# acronym 'McDonald'
-
# underscore 'McDonald' #=> 'mcdonald'
-
# camelize 'mcdonald' #=> 'McDonald'
-
1
def acronym(word)
-
@acronyms[word.downcase] = word
-
@acronym_regex = /#{@acronyms.values.join("|")}/
-
end
-
-
# Specifies a new pluralization rule and its replacement. The rule can
-
# either be a string or a regular expression. The replacement should
-
# always be a string that may include references to the matched data from
-
# the rule.
-
1
def plural(rule, replacement)
-
39
@uncountables.delete(rule) if rule.is_a?(String)
-
39
@uncountables.delete(replacement)
-
39
@plurals.prepend([rule, replacement])
-
end
-
-
# Specifies a new singularization rule and its replacement. The rule can
-
# either be a string or a regular expression. The replacement should
-
# always be a string that may include references to the matched data from
-
# the rule.
-
1
def singular(rule, replacement)
-
36
@uncountables.delete(rule) if rule.is_a?(String)
-
36
@uncountables.delete(replacement)
-
36
@singulars.prepend([rule, replacement])
-
end
-
-
# Specifies a new irregular that applies to both pluralization and
-
# singularization at the same time. This can only be used for strings, not
-
# regular expressions. You simply pass the irregular in singular and
-
# plural form.
-
#
-
# irregular 'octopus', 'octopi'
-
# irregular 'person', 'people'
-
1
def irregular(singular, plural)
-
8
@uncountables.delete(singular)
-
8
@uncountables.delete(plural)
-
8
if singular[0,1].upcase == plural[0,1].upcase
-
7
plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
-
7
plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
-
7
singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
-
else
-
1
plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
-
1
plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
-
1
plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
-
1
plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
-
1
singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
-
1
singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
-
end
-
end
-
-
# Add uncountable words that shouldn't be attempted inflected.
-
#
-
# uncountable 'money'
-
# uncountable 'money', 'information'
-
# uncountable %w( money information rice )
-
1
def uncountable(*words)
-
1
(@uncountables << words).flatten!
-
end
-
-
# Specifies a humanized form of a string by a regular expression rule or
-
# by a string mapping. When using a regular expression based replacement,
-
# the normal humanize formatting is called after the replacement. When a
-
# string is used, the human form should be specified as desired (example:
-
# 'The name', not 'the_name').
-
#
-
# human /_cnt$/i, '\1_count'
-
# human 'legacy_col_person_name', 'Name'
-
1
def human(rule, replacement)
-
@humans.prepend([rule, replacement])
-
end
-
-
# Clears the loaded inflections within a given scope (default is
-
# <tt>:all</tt>). Give the scope as a symbol of the inflection type, the
-
# options are: <tt>:plurals</tt>, <tt>:singulars</tt>, <tt>:uncountables</tt>,
-
# <tt>:humans</tt>.
-
#
-
# clear :all
-
# clear :plurals
-
1
def clear(scope = :all)
-
case scope
-
when :all
-
@plurals, @singulars, @uncountables, @humans = [], [], [], []
-
else
-
instance_variable_set "@#{scope}", []
-
end
-
end
-
end
-
-
# Yields a singleton instance of Inflector::Inflections so you can specify
-
# additional inflector rules. If passed an optional locale, rules for other
-
# languages can be specified. If not specified, defaults to <tt>:en</tt>.
-
# Only rules for English are provided.
-
#
-
# ActiveSupport::Inflector.inflections(:en) do |inflect|
-
# inflect.uncountable 'rails'
-
# end
-
1
def inflections(locale = :en)
-
11515
if block_given?
-
2
yield Inflections.instance(locale)
-
else
-
11513
Inflections.instance(locale)
-
end
-
end
-
end
-
end
-
# encoding: utf-8
-
-
1
require 'active_support/inflector/inflections'
-
1
require 'active_support/inflections'
-
-
1
module ActiveSupport
-
# The Inflector transforms words from singular to plural, class names to table
-
# names, modularized class names to ones without, and class names to foreign
-
# keys. The default inflections for pluralization, singularization, and
-
# uncountable words are kept in inflections.rb.
-
#
-
# The Rails core team has stated patches for the inflections library will not
-
# be accepted in order to avoid breaking legacy applications which may be
-
# relying on errant inflections. If you discover an incorrect inflection and
-
# require it for your application or wish to define rules for languages other
-
# than English, please correct or add them yourself (explained below).
-
1
module Inflector
-
1
extend self
-
-
# Returns the plural form of the word in the string.
-
#
-
# If passed an optional +locale+ parameter, the word will be
-
# pluralized using rules defined for that language. By default,
-
# this parameter is set to <tt>:en</tt>.
-
#
-
# 'post'.pluralize # => "posts"
-
# 'octopus'.pluralize # => "octopi"
-
# 'sheep'.pluralize # => "sheep"
-
# 'words'.pluralize # => "words"
-
# 'CamelOctopus'.pluralize # => "CamelOctopi"
-
# 'ley'.pluralize(:es) # => "leyes"
-
1
def pluralize(word, locale = :en)
-
179
apply_inflections(word, inflections(locale).plurals)
-
end
-
-
# The reverse of +pluralize+, returns the singular form of a word in a
-
# string.
-
#
-
# If passed an optional +locale+ parameter, the word will be
-
# pluralized using rules defined for that language. By default,
-
# this parameter is set to <tt>:en</tt>.
-
#
-
# 'posts'.singularize # => "post"
-
# 'octopi'.singularize # => "octopus"
-
# 'sheep'.singularize # => "sheep"
-
# 'word'.singularize # => "word"
-
# 'CamelOctopi'.singularize # => "CamelOctopus"
-
# 'leyes'.singularize(:es) # => "ley"
-
1
def singularize(word, locale = :en)
-
744
apply_inflections(word, inflections(locale).singulars)
-
end
-
-
# By default, +camelize+ converts strings to UpperCamelCase. If the argument
-
# to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
-
# lowerCamelCase.
-
#
-
# +camelize+ will also convert '/' to '::' which is useful for converting
-
# paths to namespaces.
-
#
-
# 'active_model'.camelize # => "ActiveModel"
-
# 'active_model'.camelize(:lower) # => "activeModel"
-
# 'active_model/errors'.camelize # => "ActiveModel::Errors"
-
# 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
-
#
-
# As a rule of thumb you can think of +camelize+ as the inverse of
-
# +underscore+, though there are cases where that does not hold:
-
#
-
# 'SSLError'.underscore.camelize # => "SslError"
-
1
def camelize(term, uppercase_first_letter = true)
-
2136
string = term.to_s
-
2136
if uppercase_first_letter
-
4272
string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
-
else
-
string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
-
end
-
2951
string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::')
-
end
-
-
# Makes an underscored, lowercase form from the expression in the string.
-
#
-
# Changes '::' to '/' to convert namespaces to paths.
-
#
-
# 'ActiveModel'.underscore # => "active_model"
-
# 'ActiveModel::Errors'.underscore # => "active_model/errors"
-
#
-
# As a rule of thumb you can think of +underscore+ as the inverse of
-
# +camelize+, though there are cases where that does not hold:
-
#
-
# 'SSLError'.underscore.camelize # => "SslError"
-
1
def underscore(camel_cased_word)
-
6469
word = camel_cased_word.to_s.dup
-
6469
word.gsub!('::', '/')
-
6469
word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
-
6469
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
-
6469
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
-
6469
word.tr!("-", "_")
-
6469
word.downcase!
-
6469
word
-
end
-
-
# Capitalizes the first word and turns underscores into spaces and strips a
-
# trailing "_id", if any. Like +titleize+, this is meant for creating pretty
-
# output.
-
#
-
# 'employee_salary'.humanize # => "Employee salary"
-
# 'author_id'.humanize # => "Author"
-
1
def humanize(lower_case_and_underscored_word)
-
74
result = lower_case_and_underscored_word.to_s.dup
-
74
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
-
74
result.gsub!(/_id$/, "")
-
74
result.tr!('_', ' ')
-
74
result.gsub(/([a-z\d]*)/i) { |match|
-
173
"#{inflections.acronyms[match] || match.downcase}"
-
73
}.gsub(/^\w/) { $&.upcase }
-
end
-
-
# Capitalizes all the words and replaces some characters in the string to
-
# create a nicer looking title. +titleize+ is meant for creating pretty
-
# output. It is not used in the Rails internals.
-
#
-
# +titleize+ is also aliased as +titlecase+.
-
#
-
# 'man from the boondocks'.titleize # => "Man From The Boondocks"
-
# 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
-
# 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
-
# 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
-
1
def titleize(word)
-
humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
-
end
-
-
# Create the name of a table like Rails does for models to table names. This
-
# method uses the +pluralize+ method on the last word in the string.
-
#
-
# 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
-
# 'egg_and_ham'.tableize # => "egg_and_hams"
-
# 'fancyCategory'.tableize # => "fancy_categories"
-
1
def tableize(class_name)
-
21
pluralize(underscore(class_name))
-
end
-
-
# Create a class name from a plural table name like Rails does for table
-
# names to models. Note that this returns a string and not a Class (To
-
# convert to an actual class follow +classify+ with +constantize+).
-
#
-
# 'egg_and_hams'.classify # => "EggAndHam"
-
# 'posts'.classify # => "Post"
-
#
-
# Singular names are not handled correctly:
-
#
-
# 'business'.classify # => "Busines"
-
1
def classify(table_name)
-
# strip out any leading schema name
-
36
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
-
end
-
-
# Replaces underscores with dashes in the string.
-
#
-
# 'puni_puni'.dasherize # => "puni-puni"
-
1
def dasherize(underscored_word)
-
16649
underscored_word.tr('_', '-')
-
end
-
-
# Removes the module part from the expression in the string.
-
#
-
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
-
# 'Inflections'.demodulize # => "Inflections"
-
#
-
# See also +deconstantize+.
-
1
def demodulize(path)
-
63
path = path.to_s
-
63
if i = path.rindex('::')
-
43
path[(i+2)..-1]
-
else
-
20
path
-
end
-
end
-
-
# Removes the rightmost segment from the constant expression in the string.
-
#
-
# 'Net::HTTP'.deconstantize # => "Net"
-
# '::Net::HTTP'.deconstantize # => "::Net"
-
# 'String'.deconstantize # => ""
-
# '::String'.deconstantize # => ""
-
# ''.deconstantize # => ""
-
#
-
# See also +demodulize+.
-
1
def deconstantize(path)
-
path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename
-
end
-
-
# Creates a foreign key name from a class name.
-
# +separate_class_name_and_id_with_underscore+ sets whether
-
# the method should put '_' between the name and 'id'.
-
#
-
# 'Message'.foreign_key # => "message_id"
-
# 'Message'.foreign_key(false) # => "messageid"
-
# 'Admin::Post'.foreign_key # => "post_id"
-
1
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
-
underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
-
end
-
-
# Tries to find a constant with the name specified in the argument string.
-
#
-
# 'Module'.constantize # => Module
-
# 'Test::Unit'.constantize # => Test::Unit
-
#
-
# The name is assumed to be the one of a top-level constant, no matter
-
# whether it starts with "::" or not. No lexical context is taken into
-
# account:
-
#
-
# C = 'outside'
-
# module M
-
# C = 'inside'
-
# C # => 'inside'
-
# 'C'.constantize # => 'outside', same as ::C
-
# end
-
#
-
# NameError is raised when the name is not in CamelCase or the constant is
-
# unknown.
-
1
def constantize(camel_cased_word)
-
1832
names = camel_cased_word.split('::')
-
1832
names.shift if names.empty? || names.first.empty?
-
-
1832
names.inject(Object) do |constant, name|
-
2414
if constant == Object
-
1832
constant.const_get(name)
-
else
-
582
candidate = constant.const_get(name)
-
491
next candidate if constant.const_defined?(name, false)
-
4
next candidate unless Object.const_defined?(name)
-
-
# Go down the ancestors to check it it's owned
-
# directly before we reach Object or the end of ancestors.
-
4
constant = constant.ancestors.inject do |const, ancestor|
-
32
break const if ancestor == Object
-
31
break ancestor if ancestor.const_defined?(name, false)
-
31
const
-
end
-
-
# owner is in Object, so raise
-
4
constant.const_get(name, false)
-
end
-
end
-
end
-
-
# Tries to find a constant with the name specified in the argument string.
-
#
-
# 'Module'.safe_constantize # => Module
-
# 'Test::Unit'.safe_constantize # => Test::Unit
-
#
-
# The name is assumed to be the one of a top-level constant, no matter
-
# whether it starts with "::" or not. No lexical context is taken into
-
# account:
-
#
-
# C = 'outside'
-
# module M
-
# C = 'inside'
-
# C # => 'inside'
-
# 'C'.safe_constantize # => 'outside', same as ::C
-
# end
-
#
-
# +nil+ is returned when the name is not in CamelCase or the constant (or
-
# part of it) is unknown.
-
#
-
# 'blargle'.safe_constantize # => nil
-
# 'UnknownModule'.safe_constantize # => nil
-
# 'UnknownModule::Foo::Bar'.safe_constantize # => nil
-
1
def safe_constantize(camel_cased_word)
-
44
begin
-
44
constantize(camel_cased_word)
-
18
rescue NameError => e
-
raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
-
18
e.name.to_s == camel_cased_word.to_s
-
rescue ArgumentError => e
-
raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
-
end
-
end
-
-
# Returns the suffix that should be added to a number to denote the position
-
# in an ordered sequence such as 1st, 2nd, 3rd, 4th.
-
#
-
# ordinal(1) # => "st"
-
# ordinal(2) # => "nd"
-
# ordinal(1002) # => "nd"
-
# ordinal(1003) # => "rd"
-
# ordinal(-11) # => "th"
-
# ordinal(-1021) # => "st"
-
1
def ordinal(number)
-
abs_number = number.to_i.abs
-
-
if (11..13).include?(abs_number % 100)
-
"th"
-
else
-
case abs_number % 10
-
when 1; "st"
-
when 2; "nd"
-
when 3; "rd"
-
else "th"
-
end
-
end
-
end
-
-
# Turns a number into an ordinal string used to denote the position in an
-
# ordered sequence such as 1st, 2nd, 3rd, 4th.
-
#
-
# ordinalize(1) # => "1st"
-
# ordinalize(2) # => "2nd"
-
# ordinalize(1002) # => "1002nd"
-
# ordinalize(1003) # => "1003rd"
-
# ordinalize(-11) # => "-11th"
-
# ordinalize(-1021) # => "-1021st"
-
1
def ordinalize(number)
-
"#{number}#{ordinal(number)}"
-
end
-
-
1
private
-
-
# Mount a regular expression that will match part by part of the constant.
-
# For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)?
-
1
def const_regexp(camel_cased_word) #:nodoc:
-
18
parts = camel_cased_word.split("::")
-
18
last = parts.pop
-
-
18
parts.reverse.inject(last) do |acc, part|
-
24
part.empty? ? acc : "#{part}(::#{acc})?"
-
end
-
end
-
-
# Applies inflection rules for +singularize+ and +pluralize+.
-
#
-
# apply_inflections('post', inflections.plurals) # => "posts"
-
# apply_inflections('posts', inflections.singulars) # => "post"
-
1
def apply_inflections(word, rules)
-
923
result = word.to_s.dup
-
-
923
if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
-
1
result
-
else
-
31347
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
-
922
result
-
end
-
end
-
end
-
end
-
# encoding: utf-8
-
1
require 'active_support/core_ext/string/multibyte'
-
1
require 'active_support/i18n'
-
-
1
module ActiveSupport
-
1
module Inflector
-
-
# Replaces non-ASCII characters with an ASCII approximation, or if none
-
# exists, a replacement character which defaults to "?".
-
#
-
# transliterate('Ærøskøbing')
-
# # => "AEroskobing"
-
#
-
# Default approximations are provided for Western/Latin characters,
-
# e.g, "ø", "ñ", "é", "ß", etc.
-
#
-
# This method is I18n aware, so you can set up custom approximations for a
-
# locale. This can be useful, for example, to transliterate German's "ü"
-
# and "ö" to "ue" and "oe", or to add support for transliterating Russian
-
# to ASCII.
-
#
-
# In order to make your custom transliterations available, you must set
-
# them as the <tt>i18n.transliterate.rule</tt> i18n key:
-
#
-
# # Store the transliterations in locales/de.yml
-
# i18n:
-
# transliterate:
-
# rule:
-
# ü: "ue"
-
# ö: "oe"
-
#
-
# # Or set them using Ruby
-
# I18n.backend.store_translations(:de, i18n: {
-
# transliterate: {
-
# rule: {
-
# 'ü' => 'ue',
-
# 'ö' => 'oe'
-
# }
-
# }
-
# })
-
#
-
# The value for <tt>i18n.transliterate.rule</tt> can be a simple Hash that
-
# maps characters to ASCII approximations as shown above, or, for more
-
# complex requirements, a Proc:
-
#
-
# I18n.backend.store_translations(:de, i18n: {
-
# transliterate: {
-
# rule: ->(string) { MyTransliterator.transliterate(string) }
-
# }
-
# })
-
#
-
# Now you can have different transliterations for each locale:
-
#
-
# I18n.locale = :en
-
# transliterate('Jürgen')
-
# # => "Jurgen"
-
#
-
# I18n.locale = :de
-
# transliterate('Jürgen')
-
# # => "Juergen"
-
1
def transliterate(string, replacement = "?")
-
I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
-
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
-
:replacement => replacement)
-
end
-
-
# Replaces special characters in a string so that it may be used as part of
-
# a 'pretty' URL.
-
#
-
# class Person
-
# def to_param
-
# "#{id}-#{name.parameterize}"
-
# end
-
# end
-
#
-
# @person = Person.find(1)
-
# # => #<Person id: 1, name: "Donald E. Knuth">
-
#
-
# <%= link_to(@person.name, person_path(@person)) %>
-
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
-
1
def parameterize(string, sep = '-')
-
# replace accented chars with their ascii equivalents
-
parameterized_string = transliterate(string)
-
# Turn unwanted chars into the separator
-
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep)
-
unless sep.nil? || sep.empty?
-
re_sep = Regexp.escape(sep)
-
# No more than one of the separator in a row.
-
parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
-
# Remove leading/trailing separator.
-
parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
-
end
-
parameterized_string.downcase
-
end
-
-
end
-
end
-
1
require 'active_support/json/decoding'
-
1
require 'active_support/json/encoding'
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
1
require 'active_support/core_ext/module/delegation'
-
1
require 'multi_json'
-
-
1
module ActiveSupport
-
# Look for and parse json strings that look like ISO 8601 times.
-
1
mattr_accessor :parse_json_times
-
-
1
module JSON
-
1
class << self
-
# Parses a JSON string (JavaScript Object Notation) into a hash.
-
# See www.json.org for more info.
-
#
-
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
-
# => {"team" => "rails", "players" => "36"}
-
1
def decode(json, options ={})
-
6
data = MultiJson.load(json, options)
-
4
if ActiveSupport.parse_json_times
-
convert_dates_from(data)
-
else
-
4
data
-
end
-
end
-
-
1
def engine
-
MultiJson.adapter
-
end
-
1
alias :backend :engine
-
-
1
def engine=(name)
-
MultiJson.use(name)
-
end
-
1
alias :backend= :engine=
-
-
1
def with_backend(name)
-
old_backend, self.backend = backend, name
-
yield
-
ensure
-
self.backend = old_backend
-
end
-
-
# Returns the class of the error that will be raised when there is an
-
# error in decoding JSON. Using this method means you won't directly
-
# depend on the ActiveSupport's JSON implementation, in case it changes
-
# in the future.
-
#
-
# begin
-
# obj = ActiveSupport::JSON.decode(some_string)
-
# rescue ActiveSupport::JSON.parse_error
-
# Rails.logger.warn("Attempted to decode invalid JSON: #{some_string}")
-
# end
-
1
def parse_error
-
MultiJson::DecodeError
-
end
-
-
1
private
-
-
1
def convert_dates_from(data)
-
case data
-
when nil
-
nil
-
when DATE_REGEX
-
begin
-
DateTime.parse(data)
-
rescue ArgumentError
-
data
-
end
-
when Array
-
data.map! { |d| convert_dates_from(d) }
-
when Hash
-
data.each do |key, value|
-
data[key] = convert_dates_from(value)
-
end
-
else
-
data
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/to_json'
-
1
require 'active_support/core_ext/module/delegation'
-
1
require 'active_support/json/variable'
-
-
1
require 'bigdecimal'
-
1
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/object/instance_variables'
-
1
require 'time'
-
1
require 'active_support/core_ext/time/conversions'
-
1
require 'active_support/core_ext/date_time/conversions'
-
1
require 'active_support/core_ext/date/conversions'
-
1
require 'set'
-
-
1
module ActiveSupport
-
1
class << self
-
1
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
-
:escape_html_entities_in_json, :escape_html_entities_in_json=,
-
:encode_big_decimal_as_string, :encode_big_decimal_as_string=,
-
:to => :'ActiveSupport::JSON::Encoding'
-
end
-
-
1
module JSON
-
# matches YAML-formatted dates
-
1
DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/
-
-
# Dumps objects in JSON (JavaScript Object Notation).
-
# See www.json.org for more info.
-
#
-
# ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
-
# # => "{\"team\":\"rails\",\"players\":\"36\"}"
-
1
def self.encode(value, options = nil)
-
25
Encoding::Encoder.new(options).encode(value)
-
end
-
-
1
module Encoding #:nodoc:
-
1
class CircularReferenceError < StandardError; end
-
-
1
class Encoder
-
1
attr_reader :options
-
-
1
def initialize(options = nil)
-
30
@options = options || {}
-
30
@seen = Set.new
-
end
-
-
1
def encode(value, use_options = true)
-
83
check_for_circular_references(value) do
-
83
jsonified = use_options ? value.as_json(options_for(value)) : value.as_json
-
83
jsonified.encode_json(self)
-
end
-
end
-
-
# like encode, but only calls as_json, without encoding to string.
-
1
def as_json(value, use_options = true)
-
37
check_for_circular_references(value) do
-
37
use_options ? value.as_json(options_for(value)) : value.as_json
-
end
-
end
-
-
1
def options_for(value)
-
85
if value.is_a?(Array) || value.is_a?(Hash)
-
# hashes and arrays need to get encoder in the options, so that
-
# they can detect circular references.
-
24
options.merge(:encoder => self)
-
else
-
61
options
-
end
-
end
-
-
1
def escape(string)
-
50
Encoding.escape(string)
-
end
-
-
1
private
-
1
def check_for_circular_references(value)
-
120
unless @seen.add?(value.__id__)
-
raise CircularReferenceError, 'object references itself'
-
end
-
120
yield
-
ensure
-
120
@seen.delete(value.__id__)
-
end
-
end
-
-
-
1
ESCAPED_CHARS = {
-
"\x00" => '\u0000', "\x01" => '\u0001', "\x02" => '\u0002',
-
"\x03" => '\u0003', "\x04" => '\u0004', "\x05" => '\u0005',
-
"\x06" => '\u0006', "\x07" => '\u0007', "\x0B" => '\u000B',
-
"\x0E" => '\u000E', "\x0F" => '\u000F', "\x10" => '\u0010',
-
"\x11" => '\u0011', "\x12" => '\u0012', "\x13" => '\u0013',
-
"\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016',
-
"\x17" => '\u0017', "\x18" => '\u0018', "\x19" => '\u0019',
-
"\x1A" => '\u001A', "\x1B" => '\u001B', "\x1C" => '\u001C',
-
"\x1D" => '\u001D', "\x1E" => '\u001E', "\x1F" => '\u001F',
-
"\010" => '\b',
-
"\f" => '\f',
-
"\n" => '\n',
-
"\r" => '\r',
-
"\t" => '\t',
-
'"' => '\"',
-
'\\' => '\\\\',
-
'>' => '\u003E',
-
'<' => '\u003C',
-
'&' => '\u0026' }
-
-
1
class << self
-
# If true, use ISO 8601 format for dates and times. Otherwise, fall back
-
# to the Active Support legacy format.
-
1
attr_accessor :use_standard_json_time_format
-
-
# If false, serializes BigDecimal objects as numeric instead of wrapping
-
# them in a string.
-
1
attr_accessor :encode_big_decimal_as_string
-
-
1
attr_accessor :escape_regex
-
1
attr_reader :escape_html_entities_in_json
-
-
1
def escape_html_entities_in_json=(value)
-
23
self.escape_regex = \
-
if @escape_html_entities_in_json = value
-
12
/[\x00-\x1F"\\><&]/
-
else
-
11
/[\x00-\x1F"\\]/
-
end
-
end
-
-
1
def escape(string)
-
50
string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
-
50
json = string.
-
gsub(escape_regex) { |s| ESCAPED_CHARS[s] }.
-
gsub(/([\xC0-\xDF][\x80-\xBF]|
-
[\xE0-\xEF][\x80-\xBF]{2}|
-
[\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s|
-
s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&')
-
}
-
50
json = %("#{json}")
-
50
json.force_encoding(::Encoding::UTF_8)
-
50
json
-
end
-
end
-
-
1
self.use_standard_json_time_format = true
-
1
self.escape_html_entities_in_json = true
-
1
self.encode_big_decimal_as_string = true
-
end
-
end
-
end
-
-
1
class Object
-
1
def as_json(options = nil) #:nodoc:
-
if respond_to?(:to_hash)
-
to_hash
-
else
-
instance_values
-
end
-
end
-
end
-
-
1
class Struct #:nodoc:
-
1
def as_json(options = nil)
-
1
Hash[members.zip(values)]
-
end
-
end
-
-
1
class TrueClass
-
1
def as_json(options = nil) #:nodoc:
-
2
self
-
end
-
-
1
def encode_json(encoder) #:nodoc:
-
1
to_s
-
end
-
end
-
-
1
class FalseClass
-
1
def as_json(options = nil) #:nodoc:
-
2
self
-
end
-
-
1
def encode_json(encoder) #:nodoc:
-
1
to_s
-
end
-
end
-
-
1
class NilClass
-
1
def as_json(options = nil) #:nodoc:
-
1
self
-
end
-
-
1
def encode_json(encoder) #:nodoc:
-
1
'null'
-
end
-
end
-
-
1
class String
-
1
def as_json(options = nil) #:nodoc:
-
67
self
-
end
-
-
1
def encode_json(encoder) #:nodoc:
-
50
encoder.escape(self)
-
end
-
end
-
-
1
class Symbol
-
1
def as_json(options = nil) #:nodoc:
-
6
to_s
-
end
-
end
-
-
1
class Numeric
-
1
def as_json(options = nil) #:nodoc:
-
8
self
-
end
-
-
1
def encode_json(encoder) #:nodoc:
-
9
to_s
-
end
-
end
-
-
1
class Float
-
# Encoding Infinity or NaN to JSON should return "null". The default returns
-
# "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]').
-
1
def as_json(options = nil) #:nodoc:
-
2
finite? ? self : nil
-
end
-
end
-
-
1
class BigDecimal
-
# A BigDecimal would be naturally represented as a JSON number. Most libraries,
-
# however, parse non-integer JSON numbers directly as floats. Clients using
-
# those libraries would get in general a wrong number and no way to recover
-
# other than manually inspecting the string with the JSON code itself.
-
#
-
# That's why a JSON string is returned. The JSON literal is not numeric, but
-
# if the other end knows by contract that the data is supposed to be a
-
# BigDecimal, it still has the chance to post-process the string and get the
-
# real value.
-
#
-
# Use <tt>ActiveSupport.use_standard_json_big_decimal_format = true</tt> to
-
# override this behaviour.
-
1
def as_json(options = nil) #:nodoc:
-
if finite?
-
ActiveSupport.encode_big_decimal_as_string ? to_s : self
-
else
-
nil
-
end
-
end
-
end
-
-
1
class Regexp
-
1
def as_json(options = nil) #:nodoc:
-
to_s
-
end
-
end
-
-
1
module Enumerable
-
1
def as_json(options = nil) #:nodoc:
-
to_a.as_json(options)
-
end
-
end
-
-
1
class Range
-
1
def as_json(options = nil) #:nodoc:
-
to_s
-
end
-
end
-
-
1
class Array
-
1
def as_json(options = nil) #:nodoc:
-
# use encoder as a proxy to call as_json on all elements, to protect from circular references
-
2
encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
-
8
map { |v| encoder.as_json(v, options) }
-
end
-
-
1
def encode_json(encoder) #:nodoc:
-
# we assume here that the encoder has already run as_json on self and the elements, so we run encode_json directly
-
8
"[#{map { |v| v.encode_json(encoder) } * ','}]"
-
end
-
end
-
-
1
class Hash
-
1
def as_json(options = nil) #:nodoc:
-
# create a subset of the hash by applying :only or :except
-
27
subset = if options
-
22
if attrs = options[:only]
-
slice(*Array(attrs))
-
elsif attrs = options[:except]
-
except(*Array(attrs))
-
else
-
22
self
-
end
-
else
-
5
self
-
end
-
-
# use encoder as a proxy to call as_json on all values in the subset, to protect from circular references
-
27
encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
-
58
Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
-
end
-
-
1
def encode_json(encoder) #:nodoc:
-
# values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be
-
# processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields);
-
-
# on the other hand, we need to run as_json on the elements, because the model representation may contain fields
-
# like Time/Date in their original (not jsonified) form, etc.
-
-
54
"{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v, false)}" } * ','}}"
-
end
-
end
-
-
1
class Time
-
1
def as_json(options = nil) #:nodoc:
-
if ActiveSupport.use_standard_json_time_format
-
xmlschema
-
else
-
%(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
-
end
-
end
-
end
-
-
1
class Date
-
1
def as_json(options = nil) #:nodoc:
-
if ActiveSupport.use_standard_json_time_format
-
strftime("%Y-%m-%d")
-
else
-
strftime("%Y/%m/%d")
-
end
-
end
-
end
-
-
1
class DateTime
-
1
def as_json(options = nil) #:nodoc:
-
if ActiveSupport.use_standard_json_time_format
-
xmlschema
-
else
-
strftime('%Y/%m/%d %H:%M:%S %z')
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
-
1
module ActiveSupport
-
1
module JSON
-
# Deprecated: A string that returns itself as its JSON-encoded form.
-
1
class Variable < String
-
1
def initialize(*args)
-
message = 'ActiveSupport::JSON::Variable is deprecated and will be removed in Rails 4.1. ' \
-
'For your own custom JSON literals, define #as_json and #encode_json yourself.'
-
ActiveSupport::Deprecation.warn message
-
super
-
end
-
-
1
def as_json(options = nil) self end #:nodoc:
-
1
def encode_json(encoder) self end #:nodoc:
-
end
-
end
-
end
-
1
module ActiveSupport
-
# lazy_load_hooks allows rails to lazily load a lot of components and thus
-
# making the app boot faster. Because of this feature now there is no need to
-
# require <tt>ActiveRecord::Base</tt> at boot time purely to apply
-
# configuration. Instead a hook is registered that applies configuration once
-
# <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is
-
# used as example but this feature can be applied elsewhere too.
-
#
-
# Here is an example where +on_load+ method is called to register a hook.
-
#
-
# initializer 'active_record.initialize_timezone' do
-
# ActiveSupport.on_load(:active_record) do
-
# self.time_zone_aware_attributes = true
-
# self.default_timezone = :utc
-
# end
-
# end
-
#
-
# When the entirety of +activerecord/lib/active_record/base.rb+ has been
-
# evaluated then +run_load_hooks+ is invoked. The very last line of
-
# +activerecord/lib/active_record/base.rb+ is:
-
#
-
# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
-
7
@load_hooks = Hash.new { |h,k| h[k] = [] }
-
7
@loaded = Hash.new { |h,k| h[k] = [] }
-
-
1
def self.on_load(name, options = {}, &block)
-
9
@loaded[name].each do |base|
-
3
execute_hook(base, options, block)
-
end
-
-
9
@load_hooks[name] << [block, options]
-
end
-
-
1
def self.execute_hook(base, options, block)
-
8
if options[:yield]
-
block.call(base)
-
else
-
8
base.instance_eval(&block)
-
end
-
end
-
-
1
def self.run_load_hooks(name, base = Object)
-
5
@loaded[name] << base
-
5
@load_hooks[name].each do |hook, options|
-
5
execute_hook(base, options, hook)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
1
require 'active_support/core_ext/class/attribute'
-
-
1
module ActiveSupport
-
# ActiveSupport::LogSubscriber is an object set to consume
-
# ActiveSupport::Notifications with the sole purpose of logging them.
-
# The log subscriber dispatches notifications to a registered object based
-
# on its given namespace.
-
#
-
# An example would be Active Record log subscriber responsible for logging
-
# queries:
-
#
-
# module ActiveRecord
-
# class LogSubscriber < ActiveSupport::LogSubscriber
-
# def sql(event)
-
# "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
-
# end
-
# end
-
# end
-
#
-
# And it's finally registered as:
-
#
-
# ActiveRecord::LogSubscriber.attach_to :active_record
-
#
-
# Since we need to know all instance methods before attaching the log
-
# subscriber, the line above should be called after your
-
# <tt>ActiveRecord::LogSubscriber</tt> definition.
-
#
-
# After configured, whenever a "sql.active_record" notification is published,
-
# it will properly dispatch the event (ActiveSupport::Notifications::Event) to
-
# the sql method.
-
#
-
# Log subscriber also has some helpers to deal with logging and automatically
-
# flushes all logs when the request finishes (via action_dispatch.callback
-
# notification) in a Rails environment.
-
1
class LogSubscriber
-
# Embed in a String to clear all previous ANSI sequences.
-
1
CLEAR = "\e[0m"
-
1
BOLD = "\e[1m"
-
-
# Colors
-
1
BLACK = "\e[30m"
-
1
RED = "\e[31m"
-
1
GREEN = "\e[32m"
-
1
YELLOW = "\e[33m"
-
1
BLUE = "\e[34m"
-
1
MAGENTA = "\e[35m"
-
1
CYAN = "\e[36m"
-
1
WHITE = "\e[37m"
-
-
1
mattr_accessor :colorize_logging
-
1
self.colorize_logging = true
-
-
1
class << self
-
1
def logger
-
@logger ||= Rails.logger if defined?(Rails)
-
@logger
-
end
-
-
1
attr_writer :logger
-
-
1
def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
-
28
log_subscribers << log_subscriber
-
-
28
log_subscriber.public_methods(false).each do |event|
-
264
next if %w{ start finish }.include?(event.to_s)
-
-
264
notifier.subscribe("#{event}.#{namespace}", log_subscriber)
-
end
-
end
-
-
1
def log_subscribers
-
52
@@log_subscribers ||= []
-
end
-
-
# Flush all log_subscribers' logger.
-
1
def flush_all!
-
logger.flush if logger.respond_to?(:flush)
-
end
-
end
-
-
1
def initialize
-
28
@queue_key = [self.class.name, object_id].join "-"
-
28
super
-
end
-
-
1
def logger
-
LogSubscriber.logger
-
end
-
-
1
def start(name, id, payload)
-
3438
return unless logger
-
-
1825
e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
-
1825
parent = event_stack.last
-
1825
parent << e if parent
-
-
1825
event_stack.push e
-
end
-
-
1
def finish(name, id, payload)
-
3438
return unless logger
-
-
1825
finished = Time.now
-
1825
event = event_stack.pop
-
1825
event.end = finished
-
1825
event.payload.merge!(payload)
-
-
1825
method = name.split('.').first
-
1825
begin
-
1825
send(method, event)
-
46
rescue Exception => e
-
46
logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
-
end
-
end
-
-
1
protected
-
-
1
%w(info debug warn error fatal unknown).each do |level|
-
6
class_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{level}(progname = nil, &block)
-
logger.#{level}(progname, &block) if logger
-
end
-
METHOD
-
end
-
-
# Set color by using a string or one of the defined constants. If a third
-
# option is set to +true+, it also adds bold to the string. This is based
-
# on the Highline implementation and will automatically append CLEAR to the
-
# end of the returned String.
-
1
def color(text, color, bold=false)
-
return text unless colorize_logging
-
color = self.class.const_get(color.upcase) if color.is_a?(Symbol)
-
bold = bold ? BOLD : ""
-
"#{bold}#{color}#{text}#{CLEAR}"
-
end
-
-
1
private
-
-
1
def event_stack
-
5475
Thread.current[@queue_key] ||= []
-
end
-
end
-
end
-
1
require 'simplecov'
-
1
SimpleCov.start
-
1
require 'active_support/log_subscriber'
-
1
require 'active_support/buffered_logger'
-
1
require 'active_support/notifications'
-
-
1
module ActiveSupport
-
1
class LogSubscriber
-
# Provides some helpers to deal with testing log subscribers by setting up
-
# notifications. Take for instance Active Record subscriber tests:
-
#
-
# class SyncLogSubscriberTest < ActiveSupport::TestCase
-
# include ActiveSupport::LogSubscriber::TestHelper
-
#
-
# def setup
-
# ActiveRecord::LogSubscriber.attach_to(:active_record)
-
# end
-
#
-
# def test_basic_query_logging
-
# Developer.all
-
# wait
-
# assert_equal 1, @logger.logged(:debug).size
-
# assert_match(/Developer Load/, @logger.logged(:debug).last)
-
# assert_match(/SELECT \* FROM "developers"/, @logger.logged(:debug).last)
-
# end
-
# end
-
#
-
# All you need to do is to ensure that your log subscriber is added to
-
# Rails::Subscriber, as in the second line of the code above. The test
-
# helpers are responsible for setting up the queue, subscriptions and
-
# turning colors in logs off.
-
#
-
# The messages are available in the @logger instance, which is a logger with
-
# limited powers (it actually does not send anything to your output), and
-
# you can collect them doing @logger.logged(level), where level is the level
-
# used in logging, like info, debug, warn and so on.
-
1
module TestHelper
-
1
def setup
-
24
@logger = MockLogger.new
-
24
@notifier = ActiveSupport::Notifications::Fanout.new
-
-
24
ActiveSupport::LogSubscriber.colorize_logging = false
-
-
24
@old_notifier = ActiveSupport::Notifications.notifier
-
24
set_logger(@logger)
-
24
ActiveSupport::Notifications.notifier = @notifier
-
end
-
-
1
def teardown
-
24
set_logger(nil)
-
24
ActiveSupport::Notifications.notifier = @old_notifier
-
end
-
-
1
class MockLogger
-
1
include ActiveSupport::Logger::Severity
-
-
1
attr_reader :flush_count
-
1
attr_accessor :level
-
-
1
def initialize(level = DEBUG)
-
26
@flush_count = 0
-
26
@level = level
-
58
@logged = Hash.new { |h,k| h[k] = [] }
-
end
-
-
1
def method_missing(level, message = nil)
-
1954
if block_given?
-
73
@logged[level] << yield
-
else
-
1881
@logged[level] << message
-
end
-
end
-
-
1
def logged(level)
-
100
@logged[level].compact.map { |l| l.to_s.strip }
-
end
-
-
1
def flush
-
@flush_count += 1
-
end
-
-
1
ActiveSupport::Logger::Severity.constants.each do |severity|
-
6
class_eval <<-EOT, __FILE__, __LINE__ + 1
-
def #{severity.downcase}?
-
#{severity} >= @level
-
end
-
EOT
-
end
-
end
-
-
# Wait notifications to be published.
-
1
def wait
-
22
@notifier.wait
-
end
-
-
# Overwrite if you use another logger in your log subscriber.
-
#
-
# def logger
-
# ActiveRecord::Base.logger = @logger
-
# end
-
1
def set_logger(logger)
-
ActiveSupport::LogSubscriber.logger = logger
-
end
-
end
-
end
-
end
-
1
require 'logger'
-
-
1
module ActiveSupport
-
1
class Logger < ::Logger
-
# Broadcasts logs to multiple loggers.
-
1
def self.broadcast(logger) # :nodoc:
-
Module.new do
-
define_method(:add) do |*args, &block|
-
logger.add(*args, &block)
-
super(*args, &block)
-
end
-
-
define_method(:<<) do |x|
-
logger << x
-
super(x)
-
end
-
-
define_method(:close) do
-
logger.close
-
super()
-
end
-
-
define_method(:progname=) do |name|
-
logger.progname = name
-
super(name)
-
end
-
-
define_method(:formatter=) do |formatter|
-
logger.formatter = formatter
-
super(formatter)
-
end
-
-
define_method(:level=) do |level|
-
logger.level = level
-
super(level)
-
end
-
end
-
end
-
-
1
def initialize(*args)
-
284
super
-
284
@formatter = SimpleFormatter.new
-
end
-
-
# Simple formatter which only displays the message.
-
1
class SimpleFormatter < ::Logger::Formatter
-
# This method is invoked when a log event occurs
-
1
def call(severity, timestamp, progname, msg)
-
10
"#{String === msg ? msg : msg.inspect}\n"
-
end
-
end
-
end
-
end
-
1
require 'base64'
-
1
require 'active_support/core_ext/object/blank'
-
-
1
module ActiveSupport
-
# +MessageVerifier+ makes it easy to generate and verify messages which are
-
# signed to prevent tampering.
-
#
-
# This is useful for cases like remember-me tokens and auto-unsubscribe links
-
# where the session store isn't suitable or available.
-
#
-
# Remember Me:
-
# cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
-
#
-
# In the authentication filter:
-
#
-
# id, time = @verifier.verify(cookies[:remember_me])
-
# if time < Time.now
-
# self.current_user = User.find(id)
-
# end
-
#
-
# By default it uses Marshal to serialize the message. If you want to use
-
# another serialization method, you can set the serializer attribute to
-
# something that responds to dump and load, e.g.:
-
#
-
# @verifier.serializer = YAML
-
1
class MessageVerifier
-
1
class InvalidSignature < StandardError; end
-
-
1
def initialize(secret, options = {})
-
48
@secret = secret
-
48
@digest = options[:digest] || 'SHA1'
-
48
@serializer = options[:serializer] || Marshal
-
end
-
-
1
def verify(signed_message)
-
25
raise InvalidSignature if signed_message.blank?
-
-
25
data, digest = signed_message.split("--")
-
25
if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
-
23
@serializer.load(::Base64.decode64(data))
-
else
-
2
raise InvalidSignature
-
end
-
end
-
-
1
def generate(value)
-
50
data = ::Base64.strict_encode64(@serializer.dump(value))
-
50
"#{data}--#{generate_digest(data)}"
-
end
-
-
1
private
-
# constant-time comparison algorithm to prevent timing attacks
-
1
def secure_compare(a, b)
-
25
return false unless a.bytesize == b.bytesize
-
-
23
l = a.unpack "C#{a.bytesize}"
-
-
23
res = 0
-
943
b.each_byte { |byte| res |= byte ^ l.shift }
-
23
res == 0
-
end
-
-
1
def generate_digest(data)
-
75
require 'openssl' unless defined?(OpenSSL)
-
75
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
-
end
-
end
-
end
-
1
module ActiveSupport #:nodoc:
-
1
module Multibyte
-
1
autoload :Chars, 'active_support/multibyte/chars'
-
1
autoload :Unicode, 'active_support/multibyte/unicode'
-
-
# The proxy class returned when calling mb_chars. You can use this accessor
-
# to configure your own proxy class so you can support other encodings. See
-
# the ActiveSupport::Multibyte::Chars implementation for an example how to
-
# do this.
-
#
-
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
-
1
def self.proxy_class=(klass)
-
@proxy_class = klass
-
end
-
-
# Returns the current proxy class.
-
1
def self.proxy_class
-
@proxy_class ||= ActiveSupport::Multibyte::Chars
-
end
-
end
-
end
-
1
require 'active_support/notifications/instrumenter'
-
1
require 'active_support/notifications/fanout'
-
-
1
module ActiveSupport
-
# = Notifications
-
#
-
# <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for
-
# Ruby.
-
#
-
# == Instrumenters
-
#
-
# To instrument an event you just need to do:
-
#
-
# ActiveSupport::Notifications.instrument('render', extra: :information) do
-
# render text: 'Foo'
-
# end
-
#
-
# That executes the block first and notifies all subscribers once done.
-
#
-
# In the example above +render+ is the name of the event, and the rest is called
-
# the _payload_. The payload is a mechanism that allows instrumenters to pass
-
# extra information to subscribers. Payloads consist of a hash whose contents
-
# are arbitrary and generally depend on the event.
-
#
-
# == Subscribers
-
#
-
# You can consume those events and the information they provide by registering
-
# a subscriber.
-
#
-
# ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
-
# name # => String, name of the event (such as 'render' from above)
-
# start # => Time, when the instrumented block started execution
-
# finish # => Time, when the instrumented block ended execution
-
# id # => String, unique ID for this notification
-
# payload # => Hash, the payload
-
# end
-
#
-
# For instance, let's store all "render" events in an array:
-
#
-
# events = []
-
#
-
# ActiveSupport::Notifications.subscribe('render') do |*args|
-
# events << ActiveSupport::Notifications::Event.new(*args)
-
# end
-
#
-
# That code returns right away, you are just subscribing to "render" events.
-
# The block is saved and will be called whenever someone instruments "render":
-
#
-
# ActiveSupport::Notifications.instrument('render', extra: :information) do
-
# render text: 'Foo'
-
# end
-
#
-
# event = events.first
-
# event.name # => "render"
-
# event.duration # => 10 (in milliseconds)
-
# event.payload # => { extra: :information }
-
#
-
# The block in the <tt>subscribe</tt> call gets the name of the event, start
-
# timestamp, end timestamp, a string with a unique identifier for that event
-
# (something like "535801666f04d0298cd6"), and a hash with the payload, in
-
# that order.
-
#
-
# If an exception happens during that particular instrumentation the payload will
-
# have a key <tt>:exception</tt> with an array of two elements as value: a string with
-
# the name of the exception class, and the exception message.
-
#
-
# As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
-
# is able to take the arguments as they come and provide an object-oriented
-
# interface to that data.
-
#
-
# It is also possible to pass an object as the second parameter passed to the
-
# <tt>subscribe</tt> method instead of a block:
-
#
-
# module ActionController
-
# class PageRequest
-
# def call(name, started, finished, unique_id, payload)
-
# Rails.logger.debug ['notification:', name, started, finished, unique_id, payload].join(' ')
-
# end
-
# end
-
# end
-
#
-
# ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new)
-
#
-
# resulting in the following output within the logs including a hash with the payload:
-
#
-
# notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 {
-
# :controller=>"Devise::SessionsController",
-
# :action=>"new",
-
# :params=>{"action"=>"new", "controller"=>"devise/sessions"},
-
# :format=>:html,
-
# :method=>"GET",
-
# :path=>"/login/sign_in",
-
# :status=>200,
-
# :view_runtime=>279.3080806732178,
-
# :db_runtime=>40.053
-
# }
-
#
-
# You can also subscribe to all events whose name matches a certain regexp:
-
#
-
# ActiveSupport::Notifications.subscribe(/render/) do |*args|
-
# ...
-
# end
-
#
-
# and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing
-
# to all events.
-
#
-
# == Temporary Subscriptions
-
#
-
# Sometimes you do not want to subscribe to an event for the entire life of
-
# the application. There are two ways to unsubscribe.
-
#
-
# WARNING: The instrumentation framework is designed for long-running subscribers,
-
# use this feature sparingly because it wipes some internal caches and that has
-
# a negative impact on performance.
-
#
-
# === Subscribe While a Block Runs
-
#
-
# You can subscribe to some event temporarily while some block runs. For
-
# example, in
-
#
-
# callback = lambda {|*args| ... }
-
# ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
-
# ...
-
# end
-
#
-
# the callback will be called for all "sql.active_record" events instrumented
-
# during the execution of the block. The callback is unsubscribed automatically
-
# after that.
-
#
-
# === Manual Unsubscription
-
#
-
# The +subscribe+ method returns a subscriber object:
-
#
-
# subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
-
# ...
-
# end
-
#
-
# To prevent that block from being called anymore, just unsubscribe passing
-
# that reference:
-
#
-
# ActiveSupport::Notifications.unsubscribe(subscriber)
-
#
-
# == Default Queue
-
#
-
# Notifications ships with a queue implementation that consumes and publish events
-
# to log subscribers in a thread. You can use any queue implementation you want.
-
#
-
1
module Notifications
-
1
class << self
-
1
attr_accessor :notifier
-
-
1
def publish(name, *args)
-
notifier.publish(name, *args)
-
end
-
-
1
def instrument(name, payload = {})
-
5714
if notifier.listening?(name)
-
10292
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
568
yield payload if block_given?
-
end
-
end
-
-
1
def subscribe(*args, &block)
-
5673
notifier.subscribe(*args, &block)
-
end
-
-
1
def subscribed(callback, *args, &block)
-
subscriber = subscribe(*args, &callback)
-
yield
-
ensure
-
unsubscribe(subscriber)
-
end
-
-
1
def unsubscribe(args)
-
5408
notifier.unsubscribe(args)
-
end
-
-
1
def instrumenter
-
5147
Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
-
end
-
end
-
-
1
self.notifier = Fanout.new
-
end
-
end
-
1
require 'mutex_m'
-
-
1
module ActiveSupport
-
1
module Notifications
-
# This is a default queue implementation that ships with Notifications.
-
# It just pushes events to all registered log subscribers.
-
#
-
# This class is thread safe. All methods are reentrant.
-
1
class Fanout
-
1
include Mutex_m
-
-
1
def initialize
-
25
@subscribers = []
-
25
@listeners_for = {}
-
25
super
-
end
-
-
1
def subscribe(pattern = nil, block = Proc.new)
-
5673
subscriber = Subscribers.new pattern, block
-
5673
synchronize do
-
5673
@subscribers << subscriber
-
5673
@listeners_for.clear
-
end
-
5673
subscriber
-
end
-
-
1
def unsubscribe(subscriber)
-
5408
synchronize do
-
143313
@subscribers.reject! { |s| s.matches?(subscriber) }
-
5408
@listeners_for.clear
-
end
-
end
-
-
1
def start(name, id, payload)
-
10313
listeners_for(name).each { |s| s.start(name, id, payload) }
-
end
-
-
1
def finish(name, id, payload)
-
10309
listeners_for(name).each { |s| s.finish(name, id, payload) }
-
end
-
-
1
def publish(name, *args)
-
listeners_for(name).each { |s| s.publish(name, *args) }
-
end
-
-
1
def listeners_for(name)
-
16018
synchronize do
-
115118
@listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
-
end
-
end
-
-
1
def listening?(name)
-
5714
listeners_for(name).any?
-
end
-
-
# This is a sync queue, so there is no waiting.
-
1
def wait
-
end
-
-
1
module Subscribers # :nodoc:
-
1
def self.new(pattern, listener)
-
5673
if listener.respond_to?(:start) and listener.respond_to?(:finish)
-
265
subscriber = Evented.new pattern, listener
-
else
-
5408
subscriber = Timed.new pattern, listener
-
end
-
-
5673
unless pattern
-
AllMessages.new(subscriber)
-
else
-
5673
subscriber
-
end
-
end
-
-
1
class Evented #:nodoc:
-
1
def initialize(pattern, delegate)
-
5673
@pattern = pattern
-
5673
@delegate = delegate
-
end
-
-
1
def start(name, id, payload)
-
3445
@delegate.start name, id, payload
-
end
-
-
1
def finish(name, id, payload)
-
3445
@delegate.finish name, id, payload
-
end
-
-
1
def subscribed_to?(name)
-
99100
@pattern === name.to_s
-
end
-
-
1
def matches?(subscriber_or_name)
-
self === subscriber_or_name ||
-
137905
@pattern && @pattern === subscriber_or_name
-
end
-
end
-
-
1
class Timed < Evented
-
1
def initialize(pattern, delegate)
-
5408
@timestack = []
-
5408
super
-
end
-
-
1
def publish(name, *args)
-
@delegate.call name, *args
-
end
-
-
1
def start(name, id, payload)
-
1715
@timestack.push Time.now
-
end
-
-
1
def finish(name, id, payload)
-
1713
started = @timestack.pop
-
1713
@delegate.call(name, started, Time.now, id, payload)
-
end
-
end
-
-
1
class AllMessages # :nodoc:
-
1
def initialize(delegate)
-
@delegate = delegate
-
end
-
-
1
def start(name, id, payload)
-
@delegate.start name, id, payload
-
end
-
-
1
def finish(name, id, payload)
-
@delegate.finish name, id, payload
-
end
-
-
1
def publish(name, *args)
-
@delegate.publish name, *args
-
end
-
-
1
def subscribed_to?(name)
-
true
-
end
-
-
1
alias :matches? :===
-
end
-
end
-
end
-
end
-
end
-
1
require 'securerandom'
-
-
1
module ActiveSupport
-
1
module Notifications
-
# Instrumentors are stored in a thread local.
-
1
class Instrumenter
-
1
attr_reader :id
-
-
1
def initialize(notifier)
-
33
@id = unique_id
-
33
@notifier = notifier
-
end
-
-
# Instrument the given block by measuring the time taken to execute it
-
# and publish it. Notice that events get sent even if an error occurs
-
# in the passed-in block.
-
1
def instrument(name, payload={})
-
5153
@notifier.start(name, @id, payload)
-
5153
begin
-
5153
yield
-
88
rescue Exception => e
-
88
payload[:exception] = [e.class.name, e.message]
-
88
raise e
-
ensure
-
5151
@notifier.finish(name, @id, payload)
-
5153
end
-
end
-
-
1
private
-
1
def unique_id
-
33
SecureRandom.hex(10)
-
end
-
end
-
-
1
class Event
-
1
attr_reader :name, :time, :transaction_id, :payload, :children
-
1
attr_accessor :end
-
-
1
def initialize(name, start, ending, transaction_id, payload)
-
1825
@name = name
-
1825
@payload = payload.dup
-
1825
@time = start
-
1825
@transaction_id = transaction_id
-
1825
@end = ending
-
1825
@children = []
-
end
-
-
1
def duration
-
832
1000.0 * (self.end - time)
-
end
-
-
1
def <<(event)
-
164
@children << event
-
end
-
-
1
def parent_of?(event)
-
@children.include? event
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/big_decimal/conversions'
-
1
require 'active_support/core_ext/object/blank'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/i18n'
-
-
1
module ActiveSupport
-
1
module NumberHelper
-
1
extend self
-
-
1
DEFAULTS = {
-
# Used in number_to_delimited
-
# These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
-
format: {
-
# Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
-
separator: ".",
-
# Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
-
delimiter: ",",
-
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
-
precision: 3,
-
# If set to true, precision will mean the number of significant digits instead
-
# of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
-
significant: false,
-
# If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
-
strip_insignificant_zeros: false
-
},
-
-
# Used in number_to_currency
-
currency: {
-
format: {
-
format: "%u%n",
-
negative_format: "-%u%n",
-
unit: "$",
-
# These five are to override number.format and are optional
-
separator: ".",
-
delimiter: ",",
-
precision: 2,
-
significant: false,
-
strip_insignificant_zeros: false
-
}
-
},
-
-
# Used in number_to_percentage
-
percentage: {
-
format: {
-
delimiter: "",
-
format: "%n%"
-
}
-
},
-
-
# Used in number_to_rounded
-
precision: {
-
format: {
-
delimiter: ""
-
}
-
},
-
-
# Used in number_to_human_size and number_to_human
-
human: {
-
format: {
-
# These five are to override number.format and are optional
-
delimiter: "",
-
precision: 3,
-
significant: true,
-
strip_insignificant_zeros: true
-
},
-
# Used in number_to_human_size
-
storage_units: {
-
# Storage units output formatting.
-
# %u is the storage unit, %n is the number (default: 2 MB)
-
format: "%n %u",
-
units: {
-
byte: "Bytes",
-
kb: "KB",
-
mb: "MB",
-
gb: "GB",
-
tb: "TB"
-
}
-
},
-
# Used in number_to_human
-
decimal_units: {
-
format: "%n %u",
-
# Decimal units output formatting
-
# By default we will only quantify some of the exponents
-
# but the commented ones might be defined or overridden
-
# by the user.
-
units: {
-
# femto: Quadrillionth
-
# pico: Trillionth
-
# nano: Billionth
-
# micro: Millionth
-
# mili: Thousandth
-
# centi: Hundredth
-
# deci: Tenth
-
unit: "",
-
# ten:
-
# one: Ten
-
# other: Tens
-
# hundred: Hundred
-
thousand: "Thousand",
-
million: "Million",
-
billion: "Billion",
-
trillion: "Trillion",
-
quadrillion: "Quadrillion"
-
}
-
}
-
}
-
}
-
-
1
DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
-
-1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
-
-
1
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
-
-
# Formats a +number+ into a US phone number (e.g., (555)
-
# 123-9876). You can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:area_code</tt> - Adds parentheses around the area code.
-
# * <tt>:delimiter</tt> - Specifies the delimiter to use
-
# (defaults to "-").
-
# * <tt>:extension</tt> - Specifies an extension to add to the
-
# end of the generated number.
-
# * <tt>:country_code</tt> - Sets the country code for the phone
-
# number.
-
# ==== Examples
-
#
-
# number_to_phone(5551234) # => 555-1234
-
# number_to_phone('5551234') # => 555-1234
-
# number_to_phone(1235551234) # => 123-555-1234
-
# number_to_phone(1235551234, area_code: true) # => (123) 555-1234
-
# number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234
-
# number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
-
# number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
-
# number_to_phone('123a456') # => 123a456
-
#
-
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
-
# # => +1.123.555.1234 x 1343
-
1
def number_to_phone(number, options = {})
-
22
return unless number
-
22
options = options.symbolize_keys
-
-
22
number = number.to_s.strip
-
22
area_code = options[:area_code]
-
22
delimiter = options[:delimiter] || "-"
-
22
extension = options[:extension]
-
22
country_code = options[:country_code]
-
-
22
if area_code
-
3
number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
-
else
-
19
number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
-
19
number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
-
end
-
-
22
str = ''
-
22
str << "+#{country_code}#{delimiter}" unless country_code.blank?
-
22
str << number
-
22
str << " x #{extension}" unless extension.blank?
-
22
str
-
end
-
-
# Formats a +number+ into a currency string (e.g., $13.65). You
-
# can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the level of precision (defaults
-
# to 2).
-
# * <tt>:unit</tt> - Sets the denomination of the currency
-
# (defaults to "$").
-
# * <tt>:separator</tt> - Sets the separator between the units
-
# (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to ",").
-
# * <tt>:format</tt> - Sets the format for non-negative numbers
-
# (defaults to "%u%n"). Fields are <tt>%u</tt> for the
-
# currency, and <tt>%n</tt> for the number.
-
# * <tt>:negative_format</tt> - Sets the format for negative
-
# numbers (defaults to prepending an hyphen to the formatted
-
# number given by <tt>:format</tt>). Accepts the same fields
-
# than <tt>:format</tt>, except <tt>%n</tt> is here the
-
# absolute value of the number.
-
#
-
# ==== Examples
-
#
-
# number_to_currency(1234567890.50) # => $1,234,567,890.50
-
# number_to_currency(1234567890.506) # => $1,234,567,890.51
-
# number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
-
# number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
-
# number_to_currency('123a456') # => $123a456
-
#
-
# number_to_currency(-1234567890.50, negative_format: '(%u%n)')
-
# # => ($1,234,567,890.50)
-
# number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '')
-
# # => £1234567890,50
-
# number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u')
-
# # => 1234567890,50 £
-
1
def number_to_currency(number, options = {})
-
20
return unless number
-
20
options = options.symbolize_keys
-
-
20
currency = i18n_format_options(options[:locale], :currency)
-
20
currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
-
-
20
defaults = default_format_options(:currency).merge!(currency)
-
20
defaults[:negative_format] = "-" + options[:format] if options[:format]
-
20
options = defaults.merge!(options)
-
-
20
unit = options.delete(:unit)
-
20
format = options.delete(:format)
-
-
20
if number.to_f.phase != 0
-
4
format = options.delete(:negative_format)
-
4
number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
-
end
-
-
20
format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
-
end
-
-
# Formats a +number+ as a percentage string (e.g., 65%). You can
-
# customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the #
-
# of significant_digits. If +false+, the # of fractional
-
# digits (defaults to +false+).
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
# * <tt>:format</tt> - Specifies the format of the percentage
-
# string The number field is <tt>%n</tt> (defaults to "%n%").
-
#
-
# ==== Examples
-
#
-
# number_to_percentage(100) # => 100.000%
-
# number_to_percentage('98') # => 98.000%
-
# number_to_percentage(100, precision: 0) # => 100%
-
# number_to_percentage(1000, delimiter: '.', separator: ,') # => 1.000,000%
-
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
-
# number_to_percentage(1000, locale: :fr) # => 1 000,000%
-
# number_to_percentage('98a') # => 98a%
-
# number_to_percentage(100, format: '%n %') # => 100 %
-
1
def number_to_percentage(number, options = {})
-
16
return unless number
-
16
options = options.symbolize_keys
-
-
16
defaults = format_options(options[:locale], :percentage)
-
16
options = defaults.merge!(options)
-
-
16
format = options[:format] || "%n%"
-
16
format.gsub('%n', self.number_to_rounded(number, options))
-
end
-
-
# Formats a +number+ with grouped thousands using +delimiter+
-
# (e.g., 12,324). You can customize the format in the +options+
-
# hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to ",").
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
#
-
# ==== Examples
-
#
-
# number_to_delimited(12345678) # => 12,345,678
-
# number_to_delimited('123456') # => 123,456
-
# number_to_delimited(12345678.05) # => 12,345,678.05
-
# number_to_delimited(12345678, delimiter: '.') # => 12.345.678
-
# number_to_delimited(12345678, delimiter: ',') # => 12,345,678
-
# number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05
-
# number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05
-
# number_to_delimited('112a') # => 112a
-
# number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
-
# # => 98 765 432,98
-
1
def number_to_delimited(number, options = {})
-
182
options = options.symbolize_keys
-
-
182
return number unless valid_float?(number)
-
-
178
options = format_options(options[:locale]).merge!(options)
-
-
178
parts = number.to_s.to_str.split('.')
-
178
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
-
178
parts.join(options[:separator])
-
end
-
-
# Formats a +number+ with the specified level of
-
# <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
-
# +:significant+ is +false+, and 5 if +:significant+ is +true+).
-
# You can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the #
-
# of significant_digits. If +false+, the # of fractional
-
# digits (defaults to +false+).
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
#
-
# ==== Examples
-
#
-
# number_to_rounded(111.2345) # => 111.235
-
# number_to_rounded(111.2345, precision: 2) # => 111.23
-
# number_to_rounded(13, precision: 5) # => 13.00000
-
# number_to_rounded(389.32314, precision: 0) # => 389
-
# number_to_rounded(111.2345, significant: true) # => 111
-
# number_to_rounded(111.2345, precision: 1, significant: true) # => 100
-
# number_to_rounded(13, precision: 5, significant: true) # => 13.000
-
# number_to_rounded(111.234, locale: :fr) # => 111,234
-
#
-
# number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
-
# # => 13
-
#
-
# number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3
-
# number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
-
# # => 1.111,23
-
1
def number_to_rounded(number, options = {})
-
171
return number unless valid_float?(number)
-
159
number = Float(number)
-
159
options = options.symbolize_keys
-
-
159
defaults = format_options(options[:locale], :precision)
-
159
options = defaults.merge!(options)
-
-
159
precision = options.delete :precision
-
159
significant = options.delete :significant
-
159
strip_insignificant_zeros = options.delete :strip_insignificant_zeros
-
-
159
if significant && precision > 0
-
96
if number == 0
-
4
digits, rounded_number = 1, 0
-
else
-
92
digits = (Math.log10(number.abs) + 1).floor
-
92
rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
-
92
digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
-
end
-
96
precision -= digits
-
96
precision = 0 if precision < 0 # don't let it be negative
-
else
-
63
rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
-
63
rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
-
end
-
159
formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options)
-
159
if strip_insignificant_zeros
-
83
escaped_separator = Regexp.escape(options[:separator])
-
83
formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
-
else
-
76
formatted_number
-
end
-
end
-
-
# Formats the bytes in +number+ into a more understandable
-
# representation (e.g., giving it 1500 yields 1.5 KB). This
-
# method is useful for reporting file sizes to users. You can
-
# customize the format in the +options+ hash.
-
#
-
# See <tt>number_to_human</tt> if you want to pretty-print a
-
# generic number.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the #
-
# of significant_digits. If +false+, the # of fractional
-
# digits (defaults to +true+)
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +true+)
-
# * <tt>:prefix</tt> - If +:si+ formats the number using the SI
-
# prefix (defaults to :binary)
-
#
-
# ==== Examples
-
#
-
# number_to_human_size(123) # => 123 Bytes
-
# number_to_human_size(1234) # => 1.21 KB
-
# number_to_human_size(12345) # => 12.1 KB
-
# number_to_human_size(1234567) # => 1.18 MB
-
# number_to_human_size(1234567890) # => 1.15 GB
-
# number_to_human_size(1234567890123) # => 1.12 TB
-
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
-
# number_to_human_size(483989, precision: 2) # => 470 KB
-
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
-
#
-
# Non-significant zeros after the fractional separator are stripped out by
-
# default (set <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
-
#
-
# number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
-
# number_to_human_size(524288000, precision: 5) # => "500 MB"
-
1
def number_to_human_size(number, options = {})
-
55
options = options.symbolize_keys
-
-
55
return number unless valid_float?(number)
-
51
number = Float(number)
-
-
51
defaults = format_options(options[:locale], :human)
-
51
options = defaults.merge!(options)
-
-
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
-
51
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
-
51
storage_units_format = translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true)
-
-
51
base = options[:prefix] == :si ? 1000 : 1024
-
-
51
if number.to_i < base
-
16
unit = translate_number_value_with_default('human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
-
16
storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
-
else
-
35
max_exp = STORAGE_UNITS.size - 1
-
35
exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
-
35
exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
-
35
number /= base ** exponent
-
-
35
unit_key = STORAGE_UNITS[exponent]
-
35
unit = translate_number_value_with_default("human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
-
-
35
formatted_number = self.number_to_rounded(number, options)
-
35
storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
-
end
-
end
-
-
# Pretty prints (formats and approximates) a number in a way it
-
# is more readable by humans (eg.: 1200000000 becomes "1.2
-
# Billion"). This is useful for numbers that can get very large
-
# (and too hard to read).
-
#
-
# See <tt>number_to_human_size</tt> if you want to print a file
-
# size.
-
#
-
# You can also define you own unit-quantifier names if you want
-
# to use other decimal units (eg.: 1500 becomes "1.5
-
# kilometers", 0.150 becomes "150 milliliters", etc). You may
-
# define a wide range of unit quantifiers, even fractional ones
-
# (centi, deci, mili, etc).
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the #
-
# of significant_digits. If +false+, the # of fractional
-
# digits (defaults to +true+)
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +true+)
-
# * <tt>:units</tt> - A Hash of unit quantifier names. Or a
-
# string containing an i18n scope where to find this hash. It
-
# might have the following keys:
-
# * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
-
# *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
-
# *<tt>:billion</tt>, <tt>:trillion</tt>,
-
# *<tt>:quadrillion</tt>
-
# * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
-
# *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
-
# *<tt>:pico</tt>, <tt>:femto</tt>
-
# * <tt>:format</tt> - Sets the format of the output string
-
# (defaults to "%n %u"). The field types are:
-
# * %u - The quantifier (ex.: 'thousand')
-
# * %n - The number
-
#
-
# ==== Examples
-
#
-
# number_to_human(123) # => "123"
-
# number_to_human(1234) # => "1.23 Thousand"
-
# number_to_human(12345) # => "12.3 Thousand"
-
# number_to_human(1234567) # => "1.23 Million"
-
# number_to_human(1234567890) # => "1.23 Billion"
-
# number_to_human(1234567890123) # => "1.23 Trillion"
-
# number_to_human(1234567890123456) # => "1.23 Quadrillion"
-
# number_to_human(1234567890123456789) # => "1230 Quadrillion"
-
# number_to_human(489939, precision: 2) # => "490 Thousand"
-
# number_to_human(489939, precision: 4) # => "489.9 Thousand"
-
# number_to_human(1234567, precision: 4,
-
# significant: false) # => "1.2346 Million"
-
# number_to_human(1234567, precision: 1,
-
# separator: ',',
-
# significant: false) # => "1,2 Million"
-
#
-
# Non-significant zeros after the decimal separator are stripped
-
# out by default (set <tt>:strip_insignificant_zeros</tt> to
-
# +false+ to change that):
-
#
-
# number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
-
# number_to_human(500000000, precision: 5) # => "500 Million"
-
#
-
# ==== Custom Unit Quantifiers
-
#
-
# You can also use your own custom unit quantifiers:
-
# number_to_human(500000, units: { unit: 'ml', thousand: 'lt' }) # => "500 lt"
-
#
-
# If in your I18n locale you have:
-
#
-
# distance:
-
# centi:
-
# one: "centimeter"
-
# other: "centimeters"
-
# unit:
-
# one: "meter"
-
# other: "meters"
-
# thousand:
-
# one: "kilometer"
-
# other: "kilometers"
-
# billion: "gazillion-distance"
-
#
-
# Then you could do:
-
#
-
# number_to_human(543934, units: :distance) # => "544 kilometers"
-
# number_to_human(54393498, units: :distance) # => "54400 kilometers"
-
# number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
-
# number_to_human(343, units: :distance, precision: 1) # => "300 meters"
-
# number_to_human(1, units: :distance) # => "1 meter"
-
# number_to_human(0.34, units: :distance) # => "34 centimeters"
-
1
def number_to_human(number, options = {})
-
49
options = options.symbolize_keys
-
-
49
return number unless valid_float?(number)
-
45
number = Float(number)
-
-
45
defaults = format_options(options[:locale], :human)
-
45
options = defaults.merge!(options)
-
-
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
-
45
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
-
45
inverted_du = DECIMAL_UNITS.invert
-
-
45
units = options.delete :units
-
45
unit_exponents = case units
-
when Hash
-
20
units
-
when String, Symbol
-
I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
-
when nil
-
25
translate_number_value_with_default("human.decimal_units.units", :locale => options[:locale], :raise => true)
-
else
-
raise ArgumentError, ":units must be a Hash or String translation scope."
-
488
end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
-
-
45
number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
-
206
display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
-
45
number /= 10 ** display_exponent
-
-
45
unit = case units
-
when Hash
-
20
units[DECIMAL_UNITS[display_exponent]]
-
when String, Symbol
-
I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
-
else
-
25
translate_number_value_with_default("human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
-
end
-
-
45
decimal_format = options[:format] || translate_number_value_with_default('human.decimal_units.format', :locale => options[:locale])
-
45
formatted_number = self.number_to_rounded(number, options)
-
45
decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip
-
end
-
-
1
def self.private_module_and_instance_method(method_name) #:nodoc:
-
5
private method_name
-
5
private_class_method method_name
-
end
-
1
private_class_method :private_module_and_instance_method
-
-
1
def format_options(locale, namespace = nil) #:nodoc:
-
449
default_format_options(namespace).merge!(i18n_format_options(locale, namespace))
-
end
-
1
private_module_and_instance_method :format_options
-
-
1
def default_format_options(namespace = nil) #:nodoc:
-
469
options = DEFAULTS[:format].dup
-
469
options.merge!(DEFAULTS[namespace][:format]) if namespace
-
469
options
-
end
-
1
private_module_and_instance_method :default_format_options
-
-
1
def i18n_format_options(locale, namespace = nil) #:nodoc:
-
469
options = I18n.translate(:'number.format', locale: locale, default: {}).dup
-
469
if namespace
-
291
options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
-
end
-
469
options
-
end
-
1
private_module_and_instance_method :i18n_format_options
-
-
1
def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
-
856
default = key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
-
-
195
I18n.translate(key, { default: default, scope: :number }.merge!(i18n_options))
-
end
-
1
private_module_and_instance_method :translate_number_value_with_default
-
-
1
def valid_float?(number) #:nodoc:
-
457
Float(number)
-
rescue ArgumentError, TypeError
-
24
false
-
end
-
1
private_module_and_instance_method :valid_float?
-
end
-
end
-
1
require 'active_support/core_ext/hash/deep_merge'
-
-
1
module ActiveSupport
-
1
class OptionMerger #:nodoc:
-
1
instance_methods.each do |method|
-
139
undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/
-
end
-
-
1
def initialize(context, options)
-
428
@context, @options = context, options
-
end
-
-
1
private
-
1
def method_missing(method, *arguments, &block)
-
829
if arguments.last.is_a?(Proc)
-
proc = arguments.pop
-
arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
-
else
-
829
arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup)
-
end
-
-
829
@context.__send__(method, *arguments, &block)
-
end
-
end
-
end
-
1
module ActiveSupport
-
# Usually key value pairs are handled something like this:
-
#
-
# h = {}
-
# h[:boy] = 'John'
-
# h[:girl] = 'Mary'
-
# h[:boy] # => 'John'
-
# h[:girl] # => 'Mary'
-
#
-
# Using +OrderedOptions+, the above code could be reduced to:
-
#
-
# h = ActiveSupport::OrderedOptions.new
-
# h.boy = 'John'
-
# h.girl = 'Mary'
-
# h.boy # => 'John'
-
# h.girl # => 'Mary'
-
1
class OrderedOptions < Hash
-
1
alias_method :_get, :[] # preserve the original #[] method
-
1
protected :_get # make it protected
-
-
1
def []=(key, value)
-
758
super(key.to_sym, value)
-
end
-
-
1
def [](key)
-
13996
super(key.to_sym)
-
end
-
-
1
def method_missing(name, *args)
-
14754
name_string = name.to_s
-
14754
if name_string.chomp!('=')
-
758
self[name_string] = args.first
-
else
-
13996
self[name]
-
end
-
end
-
-
1
def respond_to_missing?(name, include_private)
-
397
true
-
end
-
end
-
-
1
class InheritableOptions < OrderedOptions
-
1
def initialize(parent = nil)
-
4719
if parent.kind_of?(OrderedOptions)
-
# use the faster _get when dealing with OrderedOptions
-
9297
super() { |h,k| parent._get(k) }
-
1640
elsif parent
-
62
super() { |h,k| parent[k] }
-
else
-
1578
super()
-
end
-
end
-
-
1
def inheritable_copy
-
3017
self.class.new(self)
-
end
-
end
-
end
-
# This is private interface.
-
#
-
# Rails components cherry pick from Active Support as needed, but there are a
-
# few features that are used for sure some way or another and it is not worth
-
# to put individual requires absolutely everywhere. Think blank? for example.
-
#
-
# This file is loaded by every Rails component except Active Support itself,
-
# but it does not belong to the Rails public interface. It is internal to
-
# Rails and can change anytime.
-
-
# Defines Object#blank? and Object#present?.
-
1
require 'active_support/core_ext/object/blank'
-
-
# Rails own autoload, eager_load, etc.
-
1
require 'active_support/dependencies/autoload'
-
-
# Support for ClassMethods and the included macro.
-
1
require 'active_support/concern'
-
-
# Defines Class#class_attribute.
-
1
require 'active_support/core_ext/class/attribute'
-
-
# Defines Module#delegate.
-
1
require 'active_support/core_ext/module/delegation'
-
-
# Defines ActiveSupport::Deprecation.
-
1
require 'active_support/deprecation'
-
1
require 'active_support/concern'
-
1
require 'active_support/core_ext/class/attribute'
-
1
require 'active_support/core_ext/proc'
-
1
require 'active_support/core_ext/string/inflections'
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
module ActiveSupport
-
# Rescuable module adds support for easier exception handling.
-
1
module Rescuable
-
1
extend Concern
-
-
1
included do
-
1
class_attribute :rescue_handlers
-
1
self.rescue_handlers = []
-
end
-
-
1
module ClassMethods
-
# Rescue exceptions raised in controller actions.
-
#
-
# <tt>rescue_from</tt> receives a series of exception classes or class
-
# names, and a trailing <tt>:with</tt> option with the name of a method
-
# or a Proc object to be called to handle them. Alternatively a block can
-
# be given.
-
#
-
# Handlers that take one argument will be called with the exception, so
-
# that the exception can be inspected when dealing with it.
-
#
-
# Handlers are inherited. They are searched from right to left, from
-
# bottom to top, and up the hierarchy. The handler of the first class for
-
# which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
-
# any.
-
#
-
# class ApplicationController < ActionController::Base
-
# rescue_from User::NotAuthorized, with: :deny_access # self defined exception
-
# rescue_from ActiveRecord::RecordInvalid, with: :show_errors
-
#
-
# rescue_from 'MyAppError::Base' do |exception|
-
# render xml: exception, status: 500
-
# end
-
#
-
# protected
-
# def deny_access
-
# ...
-
# end
-
#
-
# def show_errors(exception)
-
# exception.record.new_record? ? ...
-
# end
-
# end
-
#
-
# Exceptions raised inside exception handlers are not propagated up.
-
1
def rescue_from(*klasses, &block)
-
21
options = klasses.extract_options!
-
-
21
unless options.has_key?(:with)
-
8
if block_given?
-
8
options[:with] = block
-
else
-
raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
-
end
-
end
-
-
21
klasses.each do |klass|
-
22
key = if klass.is_a?(Class) && klass <= Exception
-
15
klass.name
-
elsif klass.is_a?(String)
-
7
klass
-
else
-
raise ArgumentError, "#{klass} is neither an Exception nor a String"
-
end
-
-
# put the new handler at the end because the list is read in reverse
-
22
self.rescue_handlers += [[key, options[:with]]]
-
end
-
end
-
end
-
-
# Tries to rescue the exception by looking up and calling a registered handler.
-
1
def rescue_with_handler(exception)
-
91
if handler = handler_for_rescue(exception)
-
24
handler.arity != 0 ? handler.call(exception) : handler.call
-
24
true # don't rely on the return value of the handler
-
end
-
end
-
-
1
def handler_for_rescue(exception)
-
# We go from right to left because pairs are pushed onto rescue_handlers
-
# as rescue_from declarations are found.
-
96
_, rescuer = self.class.rescue_handlers.reverse.detect do |klass_name, handler|
-
# The purpose of allowing strings in rescue_from is to support the
-
# declaration of handler associations for exception classes whose
-
# definition is yet unknown.
-
#
-
# Since this loop needs the constants it would be inconsistent to
-
# assume they should exist at this point. An early raised exception
-
# could trigger some other handler and the array could include
-
# precisely a string whose corresponding constant has not yet been
-
# seen. This is why we are tolerant to unknown constants.
-
#
-
# Note that this tolerance only matters if the exception was given as
-
# a string, otherwise a NameError will be raised by the interpreter
-
# itself when rescue_from CONSTANT is executed.
-
208
klass = self.class.const_get(klass_name) rescue nil
-
208
klass ||= klass_name.constantize rescue nil
-
208
exception.is_a?(klass) if klass
-
end
-
-
96
case rescuer
-
when Symbol
-
5
method(rescuer)
-
when Proc
-
20
if rescuer.arity == 0
-
25
Proc.new { instance_exec(&rescuer) }
-
else
-
14
Proc.new { |_exception| instance_exec(_exception, &rescuer) }
-
end
-
end
-
end
-
end
-
end
-
1
gem 'minitest' # make sure we get the gem, not stdlib
-
1
require 'minitest/spec'
-
1
require 'active_support/testing/tagged_logging'
-
1
require 'active_support/testing/setup_and_teardown'
-
1
require 'active_support/testing/assertions'
-
1
require 'active_support/testing/deprecation'
-
1
require 'active_support/testing/isolation'
-
1
require 'active_support/testing/mocha_module'
-
1
require 'active_support/testing/constant_lookup'
-
1
require 'active_support/core_ext/kernel/reporting'
-
1
require 'active_support/deprecation'
-
-
1
module ActiveSupport
-
1
class TestCase < ::MiniTest::Spec
-
-
1
include ActiveSupport::Testing::MochaModule
-
-
# Use AS::TestCase for the base class when describing a model
-
1
register_spec_type(self) do |desc|
-
20
Class === desc && desc < ActiveRecord::Base
-
end
-
-
1
Assertion = MiniTest::Assertion
-
1
alias_method :method_name, :__name__
-
-
1
$tags = {}
-
1
def self.for_tag(tag)
-
yield if $tags[tag]
-
end
-
-
# FIXME: we have tests that depend on run order, we should fix that and
-
# remove this method.
-
1
def self.test_order # :nodoc:
-
760
:sorted
-
end
-
-
1
include ActiveSupport::Testing::TaggedLogging
-
1
include ActiveSupport::Testing::SetupAndTeardown
-
1
include ActiveSupport::Testing::Assertions
-
1
include ActiveSupport::Testing::Deprecation
-
-
1
def self.describe(text)
-
36
if block_given?
-
36
super
-
else
-
message = "`describe` without a block is deprecated, please switch to: `def self.name; #{text.inspect}; end`\n"
-
ActiveSupport::Deprecation.warn message
-
-
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
-
def self.name
-
"#{text}"
-
end
-
RUBY_EVAL
-
end
-
end
-
-
1
class << self
-
1
alias :test :it
-
end
-
-
# test/unit backwards compatibility methods
-
1
alias :assert_raise :assert_raises
-
1
alias :assert_not_nil :refute_nil
-
1
alias :assert_not_equal :refute_equal
-
1
alias :assert_no_match :refute_match
-
1
alias :assert_not_same :refute_same
-
-
# Fails if the block raises an exception.
-
#
-
# assert_nothing_raised do
-
# ...
-
# end
-
1
def assert_nothing_raised(*args)
-
140
yield
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/blank'
-
-
1
module ActiveSupport
-
1
module Testing
-
1
module Assertions
-
# Test numeric difference between the return value of an expression as a
-
# result of what is evaluated in the yielded block.
-
#
-
# assert_difference 'Article.count' do
-
# post :create, article: {...}
-
# end
-
#
-
# An arbitrary expression is passed in and evaluated.
-
#
-
# assert_difference 'assigns(:article).comments(:reload).size' do
-
# post :create, comment: {...}
-
# end
-
#
-
# An arbitrary positive or negative difference can be specified.
-
# The default is <tt>1</tt>.
-
#
-
# assert_difference 'Article.count', -1 do
-
# post :delete, id: ...
-
# end
-
#
-
# An array of expressions can also be passed in and evaluated.
-
#
-
# assert_difference [ 'Article.count', 'Post.count' ], 2 do
-
# post :create, article: {...}
-
# end
-
#
-
# A lambda or a list of lambdas can be passed in and evaluated:
-
#
-
# assert_difference ->{ Article.count }, 2 do
-
# post :create, article: {...}
-
# end
-
#
-
# assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
-
# post :create, article: {...}
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_difference 'Article.count', -1, 'An Article should be destroyed' do
-
# post :delete, id: ...
-
# end
-
1
def assert_difference(expression, difference = 1, message = nil, &block)
-
6
expressions = Array(expression)
-
-
6
exps = expressions.map { |e|
-
18
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
-
}
-
12
before = exps.map { |e| e.call }
-
-
6
yield
-
-
6
expressions.zip(exps).each_with_index do |(code, e), i|
-
6
error = "#{code.inspect} didn't change by #{difference}"
-
6
error = "#{message}.\n#{error}" if message
-
6
assert_equal(before[i] + difference, e.call, error)
-
end
-
end
-
-
# Assertion that the numeric result of evaluating an expression is not
-
# changed before and after invoking the passed in block.
-
#
-
# assert_no_difference 'Article.count' do
-
# post :create, article: invalid_attributes
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_no_difference 'Article.count', 'An Article should not be created' do
-
# post :create, article: invalid_attributes
-
# end
-
1
def assert_no_difference(expression, message = nil, &block)
-
assert_difference expression, 0, message, &block
-
end
-
-
# Test if an expression is blank. Passes if <tt>object.blank?</tt>
-
# is +true+.
-
#
-
# assert_blank [] # => true
-
# assert_blank [[]] # => [[]] is not blank
-
#
-
# An error message can be specified.
-
#
-
# assert_blank [], 'this should be blank'
-
1
def assert_blank(object, message=nil)
-
12
message ||= "#{object.inspect} is not blank"
-
12
assert object.blank?, message
-
end
-
-
# Test if an expression is not blank. Passes if <tt>object.present?</tt>
-
# is +true+.
-
#
-
# assert_present({ data: 'x' }) # => true
-
# assert_present({}) # => {} is blank
-
#
-
# An error message can be specified.
-
#
-
# assert_present({ data: 'x' }, 'this should not be blank')
-
1
def assert_present(object, message=nil)
-
3
message ||= "#{object.inspect} is blank"
-
3
assert object.present?, message
-
end
-
end
-
end
-
end
-
1
require "active_support/concern"
-
1
require "active_support/inflector"
-
-
1
module ActiveSupport
-
1
module Testing
-
# Resolves a constant from a minitest spec name.
-
#
-
# Given the following spec-style test:
-
#
-
# describe WidgetsController, :index do
-
# describe "authenticated user" do
-
# describe "returns widgets" do
-
# it "has a controller that exists" do
-
# assert_kind_of WidgetsController, @controller
-
# end
-
# end
-
# end
-
# end
-
#
-
# The test will have the following name:
-
#
-
# "WidgetsController::index::authenticated user::returns widgets"
-
#
-
# The constant WidgetsController can be resolved from the name.
-
# The following code will resolve the constant:
-
#
-
# controller = determine_constant_from_test_name(name) do |constant|
-
# Class === constant && constant < ::ActionController::Metal
-
# end
-
1
module ConstantLookup
-
1
extend ::ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
def determine_constant_from_test_name(test_name)
-
572
names = test_name.split "::"
-
572
while names.size > 0 do
-
712
names.last.sub!(/Test$/, "")
-
712
begin
-
712
constant = names.join("::").constantize
-
136
break(constant) if yield(constant)
-
rescue NameError
-
# Constant wasn't found, move on
-
ensure
-
712
names.pop
-
end
-
end
-
end
-
end
-
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
-
1
module ActiveSupport
-
1
module Testing
-
1
module Deprecation #:nodoc:
-
1
def assert_deprecated(match = nil, &block)
-
28
result, warnings = collect_deprecations(&block)
-
28
assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
-
28
if match
-
17
match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
-
34
assert warnings.any? { |w| w =~ match }, "No deprecation warning matched #{match}: #{warnings.join(', ')}"
-
end
-
28
result
-
end
-
-
1
def assert_not_deprecated(&block)
-
2
result, deprecations = collect_deprecations(&block)
-
2
assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
-
2
result
-
end
-
-
1
private
-
1
def collect_deprecations
-
30
old_behavior = ActiveSupport::Deprecation.behavior
-
30
deprecations = []
-
30
ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack|
-
69
deprecations << message
-
end
-
30
result = yield
-
30
[result, deprecations]
-
ensure
-
30
ActiveSupport::Deprecation.behavior = old_behavior
-
end
-
end
-
end
-
end
-
1
require 'rbconfig'
-
1
module ActiveSupport
-
1
module Testing
-
1
class RemoteError < StandardError
-
-
1
attr_reader :message, :backtrace
-
-
1
def initialize(exception)
-
@message = "caught #{exception.class.name}: #{exception.message}"
-
@backtrace = exception.backtrace
-
end
-
end
-
-
1
class ProxyTestResult
-
1
def initialize
-
@calls = []
-
end
-
-
1
def add_error(e)
-
e = Test::Unit::Error.new(e.test_name, RemoteError.new(e.exception))
-
@calls << [:add_error, e]
-
end
-
-
1
def __replay__(result)
-
@calls.each do |name, args|
-
result.send(name, *args)
-
end
-
end
-
-
1
def method_missing(name, *args)
-
@calls << [name, args]
-
end
-
end
-
-
1
module Isolation
-
1
require 'thread'
-
-
1
class ParallelEach
-
1
include Enumerable
-
-
# default to 2 cores
-
1
CORES = (ENV['TEST_CORES'] || 2).to_i
-
-
1
def initialize list
-
@list = list
-
@queue = SizedQueue.new CORES
-
end
-
-
1
def grep pattern
-
self.class.new super
-
end
-
-
1
def each
-
threads = CORES.times.map {
-
Thread.new {
-
while job = @queue.pop
-
yield job
-
end
-
}
-
}
-
@list.each { |i| @queue << i }
-
CORES.times { @queue << nil }
-
threads.each(&:join)
-
end
-
end
-
-
1
def self.included(klass) #:nodoc:
-
klass.extend(Module.new {
-
def test_methods
-
ParallelEach.new super
-
end
-
})
-
end
-
-
1
def self.forking_env?
-
1
!ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
-
end
-
-
1
def _run_class_setup # class setup method should only happen in parent
-
unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST']
-
self.class.setup if self.class.respond_to?(:setup)
-
@@ran_class_setup = true
-
end
-
end
-
-
1
def run(runner)
-
_run_class_setup
-
-
serialized = run_in_isolation do |isolated_runner|
-
super(isolated_runner)
-
end
-
-
retval, proxy = Marshal.load(serialized)
-
proxy.__replay__(runner)
-
retval
-
end
-
-
1
module Forking
-
1
def run_in_isolation(&blk)
-
read, write = IO.pipe
-
-
pid = fork do
-
read.close
-
proxy = ProxyTestResult.new
-
retval = yield proxy
-
write.puts [Marshal.dump([retval, proxy])].pack("m")
-
exit!
-
end
-
-
write.close
-
result = read.read
-
Process.wait2(pid)
-
return result.unpack("m")[0]
-
end
-
end
-
-
1
module Subprocess
-
1
ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV)
-
-
# Crazy H4X to get this working in windows / jruby with
-
# no forking.
-
1
def run_in_isolation(&blk)
-
require "tempfile"
-
-
if ENV["ISOLATION_TEST"]
-
proxy = ProxyTestResult.new
-
retval = yield proxy
-
File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
-
file.puts [Marshal.dump([retval, proxy])].pack("m")
-
end
-
exit!
-
else
-
Tempfile.open("isolation") do |tmpfile|
-
ENV["ISOLATION_TEST"] = @method_name
-
ENV["ISOLATION_OUTPUT"] = tmpfile.path
-
-
load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ")
-
`#{Gem.ruby} #{load_paths} #{$0} #{ORIG_ARGV.join(" ")} -t\"#{self.class}\"`
-
-
ENV.delete("ISOLATION_TEST")
-
ENV.delete("ISOLATION_OUTPUT")
-
-
return tmpfile.read.unpack("m")[0]
-
end
-
end
-
end
-
end
-
-
1
include forking_env? ? Forking : Subprocess
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
module Testing
-
1
module MochaModule
-
1
begin
-
1
require 'mocha/api'
-
1
include Mocha::API
-
-
1
def before_setup
-
3948
mocha_setup
-
3948
super
-
end
-
-
1
def after_teardown
-
3948
super
-
3948
mocha_verify
-
3948
mocha_teardown
-
end
-
rescue LoadError
-
end
-
end
-
end
-
end
-
1
require 'active_support/concern'
-
1
require 'active_support/callbacks'
-
-
1
module ActiveSupport
-
1
module Testing
-
1
module SetupAndTeardown
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
include ActiveSupport::Callbacks
-
1
define_callbacks :setup, :teardown
-
end
-
-
1
module ClassMethods
-
1
def setup(*args, &block)
-
12
set_callback(:setup, :before, *args, &block)
-
end
-
-
1
def teardown(*args, &block)
-
6
set_callback(:teardown, :after, *args, &block)
-
end
-
end
-
-
1
def before_setup
-
3948
super
-
3948
run_callbacks :setup
-
end
-
-
1
def after_teardown
-
3948
run_callbacks :teardown
-
3948
super
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
module Testing
-
1
module TaggedLogging
-
1
attr_writer :tagged_logger
-
-
1
def before_setup
-
3948
tagged_logger.push_tags(self.class.name, __name__) if tagged_logging?
-
3948
super
-
end
-
-
1
def after_teardown
-
3948
super
-
3948
tagged_logger.pop_tags(2) if tagged_logging?
-
end
-
-
1
private
-
1
def tagged_logger
-
7896
@tagged_logger ||= (defined?(Rails.logger) && Rails.logger)
-
end
-
-
1
def tagged_logging?
-
7896
tagged_logger && tagged_logger.respond_to?(:push_tags)
-
end
-
end
-
end
-
end
-
1
require 'active_support'
-
-
1
module ActiveSupport
-
1
autoload :Duration, 'active_support/duration'
-
1
autoload :TimeWithZone, 'active_support/time_with_zone'
-
1
autoload :TimeZone, 'active_support/values/time_zone'
-
end
-
-
1
require 'date'
-
1
require 'time'
-
-
1
require 'active_support/core_ext/time/marshal'
-
1
require 'active_support/core_ext/time/acts_like'
-
1
require 'active_support/core_ext/time/calculations'
-
1
require 'active_support/core_ext/time/conversions'
-
1
require 'active_support/core_ext/time/zones'
-
-
1
require 'active_support/core_ext/date/acts_like'
-
1
require 'active_support/core_ext/date/calculations'
-
1
require 'active_support/core_ext/date/conversions'
-
1
require 'active_support/core_ext/date/zones'
-
-
1
require 'active_support/core_ext/date_time/acts_like'
-
1
require 'active_support/core_ext/date_time/calculations'
-
1
require 'active_support/core_ext/date_time/conversions'
-
1
require 'active_support/core_ext/date_time/zones'
-
-
1
require 'active_support/core_ext/integer/time'
-
1
require 'active_support/core_ext/numeric/time'
-
1
require 'active_support/values/time_zone'
-
1
require 'active_support/core_ext/object/acts_like'
-
-
1
module ActiveSupport
-
# A Time-like class that can represent a time in any time zone. Necessary
-
# because standard Ruby Time instances are limited to UTC and the
-
# system's <tt>ENV['TZ']</tt> zone.
-
#
-
# You shouldn't ever need to create a TimeWithZone instance directly via +new+.
-
# Instead use methods +local+, +parse+, +at+ and +now+ on TimeZone instances,
-
# and +in_time_zone+ on Time and DateTime instances.
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
-
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
-
# Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45 EST -05:00
-
# Time.zone.at(1170361845) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
-
# Time.zone.now # => Sun, 18 May 2008 13:07:55 EDT -04:00
-
# Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -05:00
-
#
-
# See Time and TimeZone for further documentation of these methods.
-
#
-
# TimeWithZone instances implement the same API as Ruby Time instances, so
-
# that Time and TimeWithZone instances are interchangeable.
-
#
-
# t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00
-
# t.hour # => 13
-
# t.dst? # => true
-
# t.utc_offset # => -14400
-
# t.zone # => "EDT"
-
# t.to_s(:rfc822) # => "Sun, 18 May 2008 13:27:25 -0400"
-
# t + 1.day # => Mon, 19 May 2008 13:27:25 EDT -04:00
-
# t.beginning_of_year # => Tue, 01 Jan 2008 00:00:00 EST -05:00
-
# t > Time.utc(1999) # => true
-
# t.is_a?(Time) # => true
-
# t.is_a?(ActiveSupport::TimeWithZone) # => true
-
1
class TimeWithZone
-
-
# Report class name as 'Time' to thwart type checking.
-
1
def self.name
-
'Time'
-
end
-
-
1
include Comparable
-
1
attr_reader :time_zone
-
-
1
def initialize(utc_time, time_zone, local_time = nil, period = nil)
-
418
@utc, @time_zone, @time = utc_time, time_zone, local_time
-
418
@period = @utc ? period : get_period_and_ensure_valid_local_time
-
end
-
-
# Returns a Time or DateTime instance that represents the time in +time_zone+.
-
1
def time
-
183
@time ||= period.to_local(@utc)
-
end
-
-
# Returns a Time or DateTime instance that represents the time in UTC.
-
1
def utc
-
846
@utc ||= period.to_utc(@time)
-
end
-
1
alias_method :comparable_time, :utc
-
1
alias_method :getgm, :utc
-
1
alias_method :getutc, :utc
-
1
alias_method :gmtime, :utc
-
-
# Returns the underlying TZInfo::TimezonePeriod.
-
1
def period
-
105
@period ||= time_zone.period_for_utc(@utc)
-
end
-
-
# Returns the simultaneous time in <tt>Time.zone</tt>, or the specified zone.
-
1
def in_time_zone(new_zone = ::Time.zone)
-
return self if time_zone == new_zone
-
utc.in_time_zone(new_zone)
-
end
-
-
# Returns a <tt>Time.local()</tt> instance of the simultaneous time in your
-
# system's <tt>ENV['TZ']</tt> zone.
-
1
def localtime
-
utc.respond_to?(:getlocal) ? utc.getlocal : utc.to_time.getlocal
-
end
-
1
alias_method :getlocal, :localtime
-
-
1
def dst?
-
period.dst?
-
end
-
1
alias_method :isdst, :dst?
-
-
1
def utc?
-
18
time_zone.name == 'UTC'
-
end
-
1
alias_method :gmt?, :utc?
-
-
1
def utc_offset
-
18
period.utc_total_offset
-
end
-
1
alias_method :gmt_offset, :utc_offset
-
1
alias_method :gmtoff, :utc_offset
-
-
1
def formatted_offset(colon = true, alternate_utc_string = nil)
-
18
utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Time uses +zone+ to display the time zone abbreviation, so we're
-
# duck-typing it.
-
1
def zone
-
6
period.zone_identifier.to_s
-
end
-
-
1
def inspect
-
"#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
-
end
-
-
1
def xmlschema(fraction_digits = 0)
-
fraction = if fraction_digits > 0
-
(".%06i" % time.usec)[0, fraction_digits + 1]
-
end
-
-
"#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}"
-
end
-
1
alias_method :iso8601, :xmlschema
-
-
# Coerces time to a string for JSON encoding. The default format is ISO 8601.
-
# You can get %Y/%m/%d %H:%M:%S +offset style by setting
-
# <tt>ActiveSupport::JSON::Encoding.use_standard_json_time_format</tt>
-
# to +false+.
-
#
-
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
-
# Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
-
# # => "2005-02-01T15:15:10Z"
-
#
-
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false
-
# Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
-
# # => "2005/02/01 15:15:10 +0000"
-
1
def as_json(options = nil)
-
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
-
xmlschema
-
else
-
%(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
-
end
-
end
-
-
1
def encode_with(coder)
-
if coder.respond_to?(:represent_object)
-
coder.represent_object(nil, utc)
-
else
-
coder.represent_scalar(nil, utc.strftime("%Y-%m-%d %H:%M:%S.%9NZ"))
-
end
-
end
-
-
1
def httpdate
-
utc.httpdate
-
end
-
-
1
def rfc2822
-
to_s(:rfc822)
-
end
-
1
alias_method :rfc822, :rfc2822
-
-
# <tt>:db</tt> format outputs time in UTC; all others output time in local.
-
# Uses TimeWithZone's +strftime+, so <tt>%Z</tt> and <tt>%z</tt> work correctly.
-
1
def to_s(format = :default)
-
if format == :db
-
utc.to_s(format)
-
elsif formatter = ::Time::DATE_FORMATS[format]
-
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
-
else
-
"#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format
-
end
-
end
-
1
alias_method :to_formatted_s, :to_s
-
-
# Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and
-
# +formatted_offset+, respectively, before passing to Time#strftime, so
-
# that zone information is correct
-
1
def strftime(format)
-
6
format = format.gsub('%Z', zone)
-
.gsub('%z', formatted_offset(false))
-
.gsub('%:z', formatted_offset(true))
-
.gsub('%::z', formatted_offset(true) + ":00")
-
6
time.strftime(format)
-
end
-
-
# Use the time in UTC for comparisons.
-
1
def <=>(other)
-
utc <=> other
-
end
-
-
1
def between?(min, max)
-
utc.between?(min, max)
-
end
-
-
1
def past?
-
utc.past?
-
end
-
-
1
def today?
-
time.today?
-
end
-
-
1
def future?
-
utc.future?
-
end
-
-
1
def eql?(other)
-
utc.eql?(other)
-
end
-
-
1
def hash
-
utc.hash
-
end
-
-
1
def +(other)
-
# If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
-
# otherwise move forward from #utc, for accuracy when moving across DST boundaries
-
366
if duration_of_variable_length?(other)
-
147
method_missing(:+, other)
-
else
-
219
result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other)
-
219
result.in_time_zone(time_zone)
-
end
-
end
-
-
1
def -(other)
-
# If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time,
-
# otherwise move backwards #utc, for accuracy when moving across DST boundaries
-
36
if other.acts_like?(:time)
-
utc.to_f - other.to_f
-
36
elsif duration_of_variable_length?(other)
-
30
method_missing(:-, other)
-
else
-
6
result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(other)
-
6
result.in_time_zone(time_zone)
-
end
-
end
-
-
1
def since(other)
-
# If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
-
# otherwise move forward from #utc, for accuracy when moving across DST boundaries
-
if duration_of_variable_length?(other)
-
method_missing(:since, other)
-
else
-
utc.since(other).in_time_zone(time_zone)
-
end
-
end
-
-
1
def ago(other)
-
since(-other)
-
end
-
-
1
def advance(options)
-
# If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time,
-
# otherwise advance from #utc, for accuracy when moving across DST boundaries
-
if options.values_at(:years, :weeks, :months, :days).any?
-
method_missing(:advance, options)
-
else
-
utc.advance(options).in_time_zone(time_zone)
-
end
-
end
-
-
1
%w(year mon month day mday wday yday hour min sec to_date).each do |method_name|
-
11
class_eval <<-EOV, __FILE__, __LINE__ + 1
-
def #{method_name} # def month
-
time.#{method_name} # time.month
-
end # end
-
EOV
-
end
-
-
1
def usec
-
time.respond_to?(:usec) ? time.usec : 0
-
end
-
-
1
def to_a
-
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
-
end
-
-
1
def to_f
-
utc.to_f
-
end
-
-
1
def to_i
-
utc.to_i
-
end
-
1
alias_method :tv_sec, :to_i
-
-
# A TimeWithZone acts like a Time, so just return +self+.
-
1
def to_time
-
396
utc
-
end
-
-
1
def to_datetime
-
utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
-
end
-
-
# So that +self+ <tt>acts_like?(:time)</tt>.
-
1
def acts_like_time?
-
true
-
end
-
-
# Say we're a Time to thwart type checking.
-
1
def is_a?(klass)
-
klass == ::Time || super
-
end
-
1
alias_method :kind_of?, :is_a?
-
-
1
def freeze
-
period; utc; time # preload instance variables before freezing
-
super
-
end
-
-
1
def marshal_dump
-
[utc, time_zone.name, time]
-
end
-
-
1
def marshal_load(variables)
-
initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc)
-
end
-
-
# Ensure proxy class responds to all methods that underlying time instance
-
# responds to.
-
1
def respond_to_missing?(sym, include_priv)
-
# consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
-
return false if sym.to_sym == :acts_like_date?
-
time.respond_to?(sym, include_priv)
-
end
-
-
# Send the missing method to +time+ instance, and wrap result in a new
-
# TimeWithZone with the existing +time_zone+.
-
1
def method_missing(sym, *args, &block)
-
177
wrap_with_time_zone time.__send__(sym, *args, &block)
-
end
-
-
1
private
-
1
def get_period_and_ensure_valid_local_time
-
# we don't want a Time.local instance enforcing its own DST rules as well,
-
# so transfer time values to a utc constructor if necessary
-
183
@time = transfer_time_values_to_utc_constructor(@time) unless @time.utc?
-
183
begin
-
183
@time_zone.period_for_local(@time)
-
rescue ::TZInfo::PeriodNotFound
-
# time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again
-
@time += 1.hour
-
retry
-
end
-
end
-
-
1
def transfer_time_values_to_utc_constructor(time)
-
6
::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:nsec) ? Rational(time.nsec, 1000) : 0)
-
end
-
-
1
def duration_of_variable_length?(obj)
-
804
ActiveSupport::Duration === obj && obj.parts.any? {|p| [:years, :months, :days].include?(p[0]) }
-
end
-
-
1
def wrap_with_time_zone(time)
-
177
if time.acts_like?(:time)
-
177
self.class.new(nil, time_zone, time)
-
elsif time.is_a?(Range)
-
wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end)
-
else
-
time
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/blank'
-
1
require 'active_support/core_ext/object/try'
-
-
1
module ActiveSupport
-
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
-
# It allows us to do the following:
-
#
-
# * Limit the set of zones provided by TZInfo to a meaningful subset of 142
-
# zones.
-
# * Retrieve and display zones with a friendlier name
-
# (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
-
# * Lazily load TZInfo::Timezone instances only when they're needed.
-
# * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+,
-
# +parse+, +at+ and +now+ methods.
-
#
-
# If you set <tt>config.time_zone</tt> in the Rails Application, you can
-
# access this TimeZone object via <tt>Time.zone</tt>:
-
#
-
# # application.rb:
-
# class Application < Rails::Application
-
# config.time_zone = 'Eastern Time (US & Canada)'
-
# end
-
#
-
# Time.zone # => #<TimeZone:0x514834...>
-
# Time.zone.name # => "Eastern Time (US & Canada)"
-
# Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
-
#
-
# The version of TZInfo bundled with Active Support only includes the
-
# definitions necessary to support the zones defined by the TimeZone class.
-
# If you need to use zones that aren't defined by TimeZone, you'll need to
-
# install the TZInfo gem (if a recent version of the gem is installed locally,
-
# this will be used instead of the bundled version.)
-
1
class TimeZone
-
# Keys are Rails TimeZone names, values are TZInfo identifiers.
-
1
MAPPING = {
-
"International Date Line West" => "Pacific/Midway",
-
"Midway Island" => "Pacific/Midway",
-
"American Samoa" => "Pacific/Pago_Pago",
-
"Hawaii" => "Pacific/Honolulu",
-
"Alaska" => "America/Juneau",
-
"Pacific Time (US & Canada)" => "America/Los_Angeles",
-
"Tijuana" => "America/Tijuana",
-
"Mountain Time (US & Canada)" => "America/Denver",
-
"Arizona" => "America/Phoenix",
-
"Chihuahua" => "America/Chihuahua",
-
"Mazatlan" => "America/Mazatlan",
-
"Central Time (US & Canada)" => "America/Chicago",
-
"Saskatchewan" => "America/Regina",
-
"Guadalajara" => "America/Mexico_City",
-
"Mexico City" => "America/Mexico_City",
-
"Monterrey" => "America/Monterrey",
-
"Central America" => "America/Guatemala",
-
"Eastern Time (US & Canada)" => "America/New_York",
-
"Indiana (East)" => "America/Indiana/Indianapolis",
-
"Bogota" => "America/Bogota",
-
"Lima" => "America/Lima",
-
"Quito" => "America/Lima",
-
"Atlantic Time (Canada)" => "America/Halifax",
-
"Caracas" => "America/Caracas",
-
"La Paz" => "America/La_Paz",
-
"Santiago" => "America/Santiago",
-
"Newfoundland" => "America/St_Johns",
-
"Brasilia" => "America/Sao_Paulo",
-
"Buenos Aires" => "America/Argentina/Buenos_Aires",
-
"Georgetown" => "America/Guyana",
-
"Greenland" => "America/Godthab",
-
"Mid-Atlantic" => "Atlantic/South_Georgia",
-
"Azores" => "Atlantic/Azores",
-
"Cape Verde Is." => "Atlantic/Cape_Verde",
-
"Dublin" => "Europe/Dublin",
-
"Edinburgh" => "Europe/London",
-
"Lisbon" => "Europe/Lisbon",
-
"London" => "Europe/London",
-
"Casablanca" => "Africa/Casablanca",
-
"Monrovia" => "Africa/Monrovia",
-
"UTC" => "Etc/UTC",
-
"Belgrade" => "Europe/Belgrade",
-
"Bratislava" => "Europe/Bratislava",
-
"Budapest" => "Europe/Budapest",
-
"Ljubljana" => "Europe/Ljubljana",
-
"Prague" => "Europe/Prague",
-
"Sarajevo" => "Europe/Sarajevo",
-
"Skopje" => "Europe/Skopje",
-
"Warsaw" => "Europe/Warsaw",
-
"Zagreb" => "Europe/Zagreb",
-
"Brussels" => "Europe/Brussels",
-
"Copenhagen" => "Europe/Copenhagen",
-
"Madrid" => "Europe/Madrid",
-
"Paris" => "Europe/Paris",
-
"Amsterdam" => "Europe/Amsterdam",
-
"Berlin" => "Europe/Berlin",
-
"Bern" => "Europe/Berlin",
-
"Rome" => "Europe/Rome",
-
"Stockholm" => "Europe/Stockholm",
-
"Vienna" => "Europe/Vienna",
-
"West Central Africa" => "Africa/Algiers",
-
"Bucharest" => "Europe/Bucharest",
-
"Cairo" => "Africa/Cairo",
-
"Helsinki" => "Europe/Helsinki",
-
"Kyiv" => "Europe/Kiev",
-
"Riga" => "Europe/Riga",
-
"Sofia" => "Europe/Sofia",
-
"Tallinn" => "Europe/Tallinn",
-
"Vilnius" => "Europe/Vilnius",
-
"Athens" => "Europe/Athens",
-
"Istanbul" => "Europe/Istanbul",
-
"Minsk" => "Europe/Minsk",
-
"Jerusalem" => "Asia/Jerusalem",
-
"Harare" => "Africa/Harare",
-
"Pretoria" => "Africa/Johannesburg",
-
"Moscow" => "Europe/Moscow",
-
"St. Petersburg" => "Europe/Moscow",
-
"Volgograd" => "Europe/Moscow",
-
"Kuwait" => "Asia/Kuwait",
-
"Riyadh" => "Asia/Riyadh",
-
"Nairobi" => "Africa/Nairobi",
-
"Baghdad" => "Asia/Baghdad",
-
"Tehran" => "Asia/Tehran",
-
"Abu Dhabi" => "Asia/Muscat",
-
"Muscat" => "Asia/Muscat",
-
"Baku" => "Asia/Baku",
-
"Tbilisi" => "Asia/Tbilisi",
-
"Yerevan" => "Asia/Yerevan",
-
"Kabul" => "Asia/Kabul",
-
"Ekaterinburg" => "Asia/Yekaterinburg",
-
"Islamabad" => "Asia/Karachi",
-
"Karachi" => "Asia/Karachi",
-
"Tashkent" => "Asia/Tashkent",
-
"Chennai" => "Asia/Kolkata",
-
"Kolkata" => "Asia/Kolkata",
-
"Mumbai" => "Asia/Kolkata",
-
"New Delhi" => "Asia/Kolkata",
-
"Kathmandu" => "Asia/Kathmandu",
-
"Astana" => "Asia/Dhaka",
-
"Dhaka" => "Asia/Dhaka",
-
"Sri Jayawardenepura" => "Asia/Colombo",
-
"Almaty" => "Asia/Almaty",
-
"Novosibirsk" => "Asia/Novosibirsk",
-
"Rangoon" => "Asia/Rangoon",
-
"Bangkok" => "Asia/Bangkok",
-
"Hanoi" => "Asia/Bangkok",
-
"Jakarta" => "Asia/Jakarta",
-
"Krasnoyarsk" => "Asia/Krasnoyarsk",
-
"Beijing" => "Asia/Shanghai",
-
"Chongqing" => "Asia/Chongqing",
-
"Hong Kong" => "Asia/Hong_Kong",
-
"Urumqi" => "Asia/Urumqi",
-
"Kuala Lumpur" => "Asia/Kuala_Lumpur",
-
"Singapore" => "Asia/Singapore",
-
"Taipei" => "Asia/Taipei",
-
"Perth" => "Australia/Perth",
-
"Irkutsk" => "Asia/Irkutsk",
-
"Ulaan Bataar" => "Asia/Ulaanbaatar",
-
"Seoul" => "Asia/Seoul",
-
"Osaka" => "Asia/Tokyo",
-
"Sapporo" => "Asia/Tokyo",
-
"Tokyo" => "Asia/Tokyo",
-
"Yakutsk" => "Asia/Yakutsk",
-
"Darwin" => "Australia/Darwin",
-
"Adelaide" => "Australia/Adelaide",
-
"Canberra" => "Australia/Melbourne",
-
"Melbourne" => "Australia/Melbourne",
-
"Sydney" => "Australia/Sydney",
-
"Brisbane" => "Australia/Brisbane",
-
"Hobart" => "Australia/Hobart",
-
"Vladivostok" => "Asia/Vladivostok",
-
"Guam" => "Pacific/Guam",
-
"Port Moresby" => "Pacific/Port_Moresby",
-
"Magadan" => "Asia/Magadan",
-
"Solomon Is." => "Pacific/Guadalcanal",
-
"New Caledonia" => "Pacific/Noumea",
-
"Fiji" => "Pacific/Fiji",
-
"Kamchatka" => "Asia/Kamchatka",
-
"Marshall Is." => "Pacific/Majuro",
-
"Auckland" => "Pacific/Auckland",
-
"Wellington" => "Pacific/Auckland",
-
"Nuku'alofa" => "Pacific/Tongatapu",
-
"Tokelau Is." => "Pacific/Fakaofo",
-
"Samoa" => "Pacific/Apia"
-
}
-
-
1
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
-
1
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
-
-
# Assumes self represents an offset from UTC in seconds (as returned from
-
# Time#utc_offset) and turns this into an +HH:MM formatted string.
-
#
-
# TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
-
1
def self.seconds_to_utc_offset(seconds, colon = true)
-
18
format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
-
18
sign = (seconds < 0 ? '-' : '+')
-
18
hours = seconds.abs / 3600
-
18
minutes = (seconds.abs % 3600) / 60
-
18
format % [sign, hours, minutes]
-
end
-
-
1
include Comparable
-
1
attr_reader :name
-
1
attr_reader :tzinfo
-
-
# Create a new TimeZone object with the given name and offset. The
-
# offset is the number of seconds that this time zone is offset from UTC
-
# (GMT). Seconds were chosen as the offset unit because that is the unit
-
# that Ruby uses to represent time zone offsets (see Time#utc_offset).
-
1
def initialize(name, utc_offset = nil, tzinfo = nil)
-
4
self.class.send(:require_tzinfo)
-
-
4
@name = name
-
4
@utc_offset = utc_offset
-
4
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
-
4
@current_period = nil
-
end
-
-
# Returns the offset of this time zone from UTC in seconds.
-
1
def utc_offset
-
if @utc_offset
-
@utc_offset
-
else
-
@current_period ||= tzinfo.try(:current_period)
-
@current_period.try(:utc_offset)
-
end
-
end
-
-
# Returns the offset of this time zone as a formatted string, of the
-
# format "+HH:MM".
-
1
def formatted_offset(colon=true, alternate_utc_string = nil)
-
utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Compare this time zone to the parameter. The two are compared first on
-
# their offsets, and then by name.
-
1
def <=>(zone)
-
result = (utc_offset <=> zone.utc_offset)
-
result = (name <=> zone.name) if result == 0
-
result
-
end
-
-
# Compare #name and TZInfo identifier to a supplied regexp, returning +true+
-
# if a match is found.
-
1
def =~(re)
-
return true if name =~ re || MAPPING[name] =~ re
-
end
-
-
# Returns a textual representation of this time zone.
-
1
def to_s
-
"(GMT#{formatted_offset}) #{name}"
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
-
# of +self+ from given values.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00
-
1
def local(*args)
-
time = Time.utc_time(*args)
-
ActiveSupport::TimeWithZone.new(nil, self, time)
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
-
# of +self+ from number of seconds since the Unix epoch.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.utc(2000).to_f # => 946684800.0
-
# Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
1
def at(secs)
-
Time.at(secs).utc.in_time_zone(self)
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
-
# of +self+ from parsed string.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# If upper components are missing from the string, they are supplied from
-
# TimeZone#now:
-
#
-
# Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
# Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
-
1
def parse(str, now=now)
-
6
date_parts = Date._parse(str)
-
6
return if date_parts.empty?
-
6
time = Time.parse(str, now) rescue DateTime.parse(str)
-
-
6
if date_parts[:offset].nil?
-
6
if date_parts[:hour] && time.hour != date_parts[:hour]
-
time = DateTime.parse(str)
-
end
-
-
6
ActiveSupport::TimeWithZone.new(nil, self, time)
-
else
-
time.in_time_zone(self)
-
end
-
end
-
-
# Returns an ActiveSupport::TimeWithZone instance representing the current
-
# time in the time zone represented by +self+.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00
-
1
def now
-
6
time_now.utc.in_time_zone(self)
-
end
-
-
# Return the current date in this time zone.
-
1
def today
-
tzinfo.now.to_date
-
end
-
-
# Adjust the given time to the simultaneous time in the time zone
-
# represented by +self+. Returns a Time.utc() instance -- if you want an
-
# ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead.
-
1
def utc_to_local(time)
-
tzinfo.utc_to_local(time)
-
end
-
-
# Adjust the given time to the simultaneous time in UTC. Returns a
-
# Time.utc() instance.
-
1
def local_to_utc(time, dst=true)
-
tzinfo.local_to_utc(time, dst)
-
end
-
-
# Available so that TimeZone instances respond like TZInfo::Timezone
-
# instances.
-
1
def period_for_utc(time)
-
3
tzinfo.period_for_utc(time)
-
end
-
-
# Available so that TimeZone instances respond like TZInfo::Timezone
-
# instances.
-
1
def period_for_local(time, dst=true)
-
183
tzinfo.period_for_local(time, dst)
-
end
-
-
1
def self.find_tzinfo(name)
-
4
TZInfo::TimezoneProxy.new(MAPPING[name] || name)
-
end
-
-
1
class << self
-
1
alias_method :create, :new
-
-
# Return a TimeZone instance with the given name, or +nil+ if no
-
# such TimeZone instance exists. (This exists to support the use of
-
# this class with the +composed_of+ macro.)
-
1
def new(name)
-
8
self[name]
-
end
-
-
# Return an array of all TimeZone objects. There are multiple
-
# TimeZone objects per time zone, in many cases, to make it easier
-
# for users to find their own time zone.
-
1
def all
-
@zones ||= zones_map.values.sort
-
end
-
-
1
def zones_map
-
@zones_map ||= begin
-
new_zones_names = MAPPING.keys - lazy_zones_map.keys
-
new_zones = Hash[new_zones_names.map { |place| [place, create(place)] }]
-
-
lazy_zones_map.merge(new_zones)
-
end
-
end
-
-
# Locate a specific time zone object. If the argument is a string, it
-
# is interpreted to mean the name of the timezone to locate. If it is a
-
# numeric value it is either the hour offset, or the second offset, of the
-
# timezone to find. (The first one with that offset will be returned.)
-
# Returns +nil+ if no such time zone is known to the system.
-
1
def [](arg)
-
10
case arg
-
when String
-
10
begin
-
10
lazy_zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
-
rescue TZInfo::InvalidTimezoneIdentifier
-
nil
-
end
-
when Numeric, ActiveSupport::Duration
-
arg *= 3600 if arg.abs <= 13
-
all.find { |z| z.utc_offset == arg.to_i }
-
else
-
raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
-
end
-
end
-
-
# A convenience method for returning a collection of TimeZone objects
-
# for time zones in the USA.
-
1
def us_zones
-
@us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
-
end
-
-
1
protected
-
-
1
def require_tzinfo
-
14
require 'tzinfo' unless defined?(::TZInfo)
-
rescue LoadError
-
$stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install"
-
raise
-
end
-
-
1
private
-
-
1
def lookup(name)
-
(tzinfo = find_tzinfo(name)) && create(tzinfo.name.freeze)
-
end
-
-
1
def lazy_zones_map
-
10
require_tzinfo
-
-
@lazy_zones_map ||= Hash.new do |hash, place|
-
3
hash[place] = create(place) if MAPPING.has_key?(place)
-
10
end
-
end
-
end
-
-
1
private
-
-
1
def time_now
-
6
Time.now
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
module VERSION #:nodoc:
-
1
MAJOR = 4
-
1
MINOR = 0
-
1
TINY = 0
-
1
PRE = "beta"
-
-
1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
-
end
-
end
-
1
require 'time'
-
1
require 'base64'
-
1
require 'active_support/core_ext/module/delegation'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
module ActiveSupport
-
# = XmlMini
-
#
-
# To use the much faster libxml parser:
-
# gem 'libxml-ruby', '=0.9.7'
-
# XmlMini.backend = 'LibXML'
-
1
module XmlMini
-
1
extend self
-
-
# This module decorates files deserialized using Hash.from_xml with
-
# the <tt>original_filename</tt> and <tt>content_type</tt> methods.
-
1
module FileLike #:nodoc:
-
1
attr_writer :original_filename, :content_type
-
-
1
def original_filename
-
6
@original_filename || 'untitled'
-
end
-
-
1
def content_type
-
6
@content_type || 'application/octet-stream'
-
end
-
end
-
-
DEFAULT_ENCODINGS = {
-
"binary" => "base64"
-
1
} unless defined?(DEFAULT_ENCODINGS)
-
-
TYPE_NAMES = {
-
"Symbol" => "symbol",
-
"Fixnum" => "integer",
-
"Bignum" => "integer",
-
"BigDecimal" => "decimal",
-
"Float" => "float",
-
"TrueClass" => "boolean",
-
"FalseClass" => "boolean",
-
"Date" => "date",
-
"DateTime" => "dateTime",
-
"Time" => "dateTime",
-
"Array" => "array",
-
"Hash" => "hash"
-
1
} unless defined?(TYPE_NAMES)
-
-
FORMATTING = {
-
14
"symbol" => Proc.new { |symbol| symbol.to_s },
-
"date" => Proc.new { |date| date.to_s(:db) },
-
"dateTime" => Proc.new { |time| time.xmlschema },
-
"binary" => Proc.new { |binary| ::Base64.encode64(binary) },
-
"yaml" => Proc.new { |yaml| yaml.to_yaml }
-
1
} unless defined?(FORMATTING)
-
-
# TODO use regexp instead of Date.parse
-
1
unless defined?(PARSING)
-
1
PARSING = {
-
"symbol" => Proc.new { |symbol| symbol.to_sym },
-
2
"date" => Proc.new { |date| ::Date.parse(date) },
-
1
"datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
-
3
"integer" => Proc.new { |integer| integer.to_i },
-
"float" => Proc.new { |float| float.to_f },
-
"decimal" => Proc.new { |number| BigDecimal(number) },
-
3
"boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) },
-
1
"string" => Proc.new { |string| string.to_s },
-
"yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml },
-
"base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) },
-
"binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) },
-
6
"file" => Proc.new { |file, entity| _parse_file(file, entity) }
-
}
-
-
1
PARSING.update(
-
"double" => PARSING["float"],
-
"dateTime" => PARSING["datetime"]
-
)
-
end
-
-
1
attr_reader :backend
-
1
delegate :parse, :to => :backend
-
-
1
def backend=(name)
-
1
if name.is_a?(Module)
-
@backend = name
-
else
-
1
require "active_support/xml_mini/#{name.downcase}"
-
1
@backend = ActiveSupport.const_get("XmlMini_#{name}")
-
end
-
end
-
-
1
def with_backend(name)
-
old_backend, self.backend = backend, name
-
yield
-
ensure
-
self.backend = old_backend
-
end
-
-
1
def to_tag(key, value, options)
-
21
type_name = options.delete(:type)
-
21
merged_options = options.merge(:root => key, :skip_instruct => true)
-
-
21
if value.is_a?(::Method) || value.is_a?(::Proc)
-
if value.arity == 1
-
value.call(merged_options)
-
else
-
value.call(merged_options, key.to_s.singularize)
-
end
-
21
elsif value.respond_to?(:to_xml)
-
3
value.to_xml(merged_options)
-
else
-
18
type_name ||= TYPE_NAMES[value.class.name]
-
18
type_name ||= value.class.name if value && !value.respond_to?(:to_str)
-
18
type_name = type_name.to_s if type_name
-
18
type_name = "dateTime" if type_name == "datetime"
-
-
18
key = rename_key(key.to_s, options)
-
-
18
attributes = options[:skip_types] || type_name.nil? ? { } : { :type => type_name }
-
18
attributes[:nil] = true if value.nil?
-
-
18
encoding = options[:encoding] || DEFAULT_ENCODINGS[type_name]
-
18
attributes[:encoding] = encoding if encoding
-
-
18
formatted_value = FORMATTING[type_name] && !value.nil? ?
-
FORMATTING[type_name].call(value) : value
-
-
18
options[:builder].tag!(key, formatted_value, attributes)
-
end
-
end
-
-
1
def rename_key(key, options = {})
-
36
camelize = options[:camelize]
-
36
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
-
36
if camelize
-
key = true == camelize ? key.camelize : key.camelize(camelize)
-
end
-
36
key = _dasherize(key) if dasherize
-
36
key
-
end
-
-
1
protected
-
-
1
def _dasherize(key)
-
# $2 must be a non-greedy regex for this to work
-
36
left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1,3]
-
36
"#{left}#{middle.tr('_ ', '--')}#{right}"
-
end
-
-
# TODO: Add support for other encodings
-
1
def _parse_binary(bin, entity) #:nodoc:
-
case entity['encoding']
-
when 'base64'
-
::Base64.decode64(bin)
-
else
-
bin
-
end
-
end
-
-
1
def _parse_file(file, entity)
-
6
f = StringIO.new(::Base64.decode64(file))
-
6
f.extend(FileLike)
-
6
f.original_filename = entity['name']
-
6
f.content_type = entity['content_type']
-
6
f
-
end
-
end
-
-
1
XmlMini.backend = 'REXML'
-
end
-
1
require 'active_support/core_ext/kernel/reporting'
-
1
require 'active_support/core_ext/object/blank'
-
1
require 'stringio'
-
-
1
module ActiveSupport
-
1
module XmlMini_REXML #:nodoc:
-
1
extend self
-
-
1
CONTENT_KEY = '__content__'.freeze
-
-
# Parse an XML Document string or IO into a simple hash.
-
#
-
# Same as XmlSimple::xml_in but doesn't shoot itself in the foot,
-
# and uses the defaults from Active Support.
-
#
-
# data::
-
# XML Document string or IO to parse
-
1
def parse(data)
-
24
if !data.respond_to?(:read)
-
24
data = StringIO.new(data || '')
-
end
-
-
24
char = data.getc
-
24
if char.nil?
-
{}
-
else
-
24
data.ungetc(char)
-
25
silence_warnings { require 'rexml/document' } unless defined?(REXML::Document)
-
24
doc = REXML::Document.new(data)
-
-
20
if doc.root
-
20
merge_element!({}, doc.root)
-
else
-
raise REXML::ParseException,
-
"The document #{doc.to_s.inspect} does not have a valid root"
-
end
-
end
-
end
-
-
1
private
-
# Convert an XML element and merge into the hash
-
#
-
# hash::
-
# Hash to merge the converted element into.
-
# element::
-
# XML element to merge into hash
-
1
def merge_element!(hash, element)
-
53
merge!(hash, element.name, collapse(element))
-
end
-
-
# Actually converts an XML document element into a data structure.
-
#
-
# element::
-
# The document element to be collapsed.
-
1
def collapse(element)
-
53
hash = get_attributes(element)
-
-
53
if element.has_elements?
-
50
element.each_element {|child| merge_element!(hash, child) }
-
17
merge_texts!(hash, element) unless empty_content?(element)
-
17
hash
-
else
-
36
merge_texts!(hash, element)
-
end
-
end
-
-
# Merge all the texts of an element into the hash
-
#
-
# hash::
-
# Hash to add the converted element to.
-
# element::
-
# XML element whose texts are to me merged into the hash
-
1
def merge_texts!(hash, element)
-
36
unless element.has_text?
-
hash
-
else
-
# must use value to prevent double-escaping
-
36
texts = ''
-
72
element.texts.each { |t| texts << t.value }
-
36
merge!(hash, CONTENT_KEY, texts)
-
end
-
end
-
-
# Adds a new key/value pair to an existing Hash. If the key to be added
-
# already exists and the existing value associated with key is not
-
# an Array, it will be wrapped in an Array. Then the new value is
-
# appended to that Array.
-
#
-
# hash::
-
# Hash to add key/value pair to.
-
# key::
-
# Key to be added.
-
# value::
-
# Value to be associated with key.
-
1
def merge!(hash, key, value)
-
89
if hash.has_key?(key)
-
4
if hash[key].instance_of?(Array)
-
1
hash[key] << value
-
else
-
3
hash[key] = [hash[key], value]
-
end
-
elsif value.instance_of?(Array)
-
hash[key] = [value]
-
else
-
85
hash[key] = value
-
end
-
89
hash
-
end
-
-
# Converts the attributes array of an XML element into a hash.
-
# Returns an empty Hash if node has no attributes.
-
#
-
# element::
-
# XML element to extract attributes from.
-
1
def get_attributes(element)
-
53
attributes = {}
-
86
element.attributes.each { |n,v| attributes[n] = v }
-
53
attributes
-
end
-
-
# Determines if a document element has text content
-
#
-
# element::
-
# XML element to be checked.
-
1
def empty_content?(element)
-
17
element.texts.join.blank?
-
end
-
end
-
end
-
# bust gem prelude
-
1
require 'bundler'
-
1
Bundler.setup
-
1
require 'active_support/deprecation'
-
1
require 'active_support/ordered_options'
-
1
require 'active_support/core_ext/object'
-
1
require 'rails/paths'
-
1
require 'rails/rack'
-
-
1
module Rails
-
1
module Configuration
-
# MiddlewareStackProxy is a proxy for the Rails middleware stack that allows
-
# you to configure middlewares in your application. It works basically as a
-
# command recorder, saving each command to be applied after initialization
-
# over the default middleware stack, so you can add, swap, or remove any
-
# middleware in Rails.
-
#
-
# You can add your own middlewares by using the +config.middleware.use+ method:
-
#
-
# config.middleware.use Magical::Unicorns
-
#
-
# This will put the <tt>Magical::Unicorns</tt> middleware on the end of the stack.
-
# You can use +insert_before+ if you wish to add a middleware before another:
-
#
-
# config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns
-
#
-
# There's also +insert_after+ which will insert a middleware after another:
-
#
-
# config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns
-
#
-
# Middlewares can also be completely swapped out and replaced with others:
-
#
-
# config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns
-
#
-
# And finally they can also be removed from the stack completely:
-
#
-
# config.middleware.delete ActionDispatch::BestStandardsSupport
-
#
-
1
class MiddlewareStackProxy
-
1
def initialize
-
@operations = []
-
end
-
-
1
def insert_before(*args, &block)
-
@operations << [__method__, args, block]
-
end
-
-
1
alias :insert :insert_before
-
-
1
def insert_after(*args, &block)
-
@operations << [__method__, args, block]
-
end
-
-
1
def swap(*args, &block)
-
@operations << [__method__, args, block]
-
end
-
-
1
def use(*args, &block)
-
@operations << [__method__, args, block]
-
end
-
-
1
def delete(*args, &block)
-
@operations << [__method__, args, block]
-
end
-
-
1
def merge_into(other) #:nodoc:
-
@operations.each do |operation, args, block|
-
other.send(operation, *args, &block)
-
end
-
other
-
end
-
end
-
-
1
class Generators #:nodoc:
-
1
attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging
-
1
attr_reader :hidden_namespaces
-
-
1
def initialize
-
@aliases = Hash.new { |h,k| h[k] = {} }
-
@options = Hash.new { |h,k| h[k] = {} }
-
@fallbacks = {}
-
@templates = []
-
@colorize_logging = true
-
@hidden_namespaces = []
-
end
-
-
1
def initialize_copy(source)
-
@aliases = @aliases.deep_dup
-
@options = @options.deep_dup
-
@fallbacks = @fallbacks.deep_dup
-
@templates = @templates.dup
-
end
-
-
1
def hide_namespace(namespace)
-
@hidden_namespaces << namespace
-
end
-
-
1
def method_missing(method, *args)
-
method = method.to_s.sub(/=$/, '').to_sym
-
-
return @options[method] if args.empty?
-
-
if method == :rails || args.first.is_a?(Hash)
-
namespace, configuration = method, args.shift
-
else
-
namespace, configuration = args.shift, args.shift
-
namespace = namespace.to_sym if namespace.respond_to?(:to_sym)
-
@options[:rails][method] = namespace
-
end
-
-
if configuration
-
aliases = configuration.delete(:aliases)
-
@aliases[namespace].merge!(aliases) if aliases
-
@options[namespace].merge!(configuration)
-
end
-
end
-
end
-
end
-
end
-
1
require 'rails/railtie'
-
1
require 'active_support/core_ext/module/delegation'
-
1
require 'pathname'
-
1
require 'rbconfig'
-
-
1
module Rails
-
# <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of
-
# functionality and share it with other applications or within a larger packaged application.
-
# Since Rails 3.0, every <tt>Rails::Application</tt> is just an engine, which allows for simple
-
# feature and application sharing.
-
#
-
# Any <tt>Rails::Engine</tt> is also a <tt>Rails::Railtie</tt>, so the same
-
# methods (like <tt>rake_tasks</tt> and +generators+) and configuration
-
# options that are available in railties can also be used in engines.
-
#
-
# == Creating an Engine
-
#
-
# In Rails versions prior to 3.0, your gems automatically behaved as engines, however,
-
# this coupled Rails to Rubygems. Since Rails 3.0, if you want a gem to automatically
-
# behave as an engine, you have to specify an +Engine+ for it somewhere inside
-
# your plugin's +lib+ folder (similar to how we specify a +Railtie+):
-
#
-
# # lib/my_engine.rb
-
# module MyEngine
-
# class Engine < Rails::Engine
-
# end
-
# end
-
#
-
# Then ensure that this file is loaded at the top of your <tt>config/application.rb</tt>
-
# (or in your +Gemfile+) and it will automatically load models, controllers and helpers
-
# inside +app+, load routes at <tt>config/routes.rb</tt>, load locales at
-
# <tt>config/locales/*</tt>, and load tasks at <tt>lib/tasks/*</tt>.
-
#
-
# == Configuration
-
#
-
# Besides the +Railtie+ configuration which is shared across the application, in a
-
# <tt>Rails::Engine</tt> you can access <tt>autoload_paths</tt>, <tt>eager_load_paths</tt>
-
# and <tt>autoload_once_paths</tt>, which, differently from a <tt>Railtie</tt>, are scoped to
-
# the current engine.
-
#
-
# class MyEngine < Rails::Engine
-
# # Add a load path for this specific Engine
-
# config.autoload_paths << File.expand_path("../lib/some/path", __FILE__)
-
#
-
# initializer "my_engine.add_middleware" do |app|
-
# app.middleware.use MyEngine::Middleware
-
# end
-
# end
-
#
-
# == Generators
-
#
-
# You can set up generators for engines with <tt>config.generators</tt> method:
-
#
-
# class MyEngine < Rails::Engine
-
# config.generators do |g|
-
# g.orm :active_record
-
# g.template_engine :erb
-
# g.test_framework :test_unit
-
# end
-
# end
-
#
-
# You can also set generators for an application by using <tt>config.app_generators</tt>:
-
#
-
# class MyEngine < Rails::Engine
-
# # note that you can also pass block to app_generators in the same way you
-
# # can pass it to generators method
-
# config.app_generators.orm :datamapper
-
# end
-
#
-
# == Paths
-
#
-
# Since Rails 3.0, applications and engines have more flexible path configuration (as
-
# opposed to the previous hardcoded path configuration). This means that you are not
-
# required to place your controllers at <tt>app/controllers</tt>, but in any place
-
# which you find convenient.
-
#
-
# For example, let's suppose you want to place your controllers in <tt>lib/controllers</tt>.
-
# You can set that as an option:
-
#
-
# class MyEngine < Rails::Engine
-
# paths["app/controllers"] = "lib/controllers"
-
# end
-
#
-
# You can also have your controllers loaded from both <tt>app/controllers</tt> and
-
# <tt>lib/controllers</tt>:
-
#
-
# class MyEngine < Rails::Engine
-
# paths["app/controllers"] << "lib/controllers"
-
# end
-
#
-
# The available paths in an engine are:
-
#
-
# class MyEngine < Rails::Engine
-
# paths["app"] # => ["app"]
-
# paths["app/controllers"] # => ["app/controllers"]
-
# paths["app/helpers"] # => ["app/helpers"]
-
# paths["app/models"] # => ["app/models"]
-
# paths["app/views"] # => ["app/views"]
-
# paths["lib"] # => ["lib"]
-
# paths["lib/tasks"] # => ["lib/tasks"]
-
# paths["config"] # => ["config"]
-
# paths["config/initializers"] # => ["config/initializers"]
-
# paths["config/locales"] # => ["config/locales"]
-
# paths["config/routes"] # => ["config/routes.rb"]
-
# end
-
#
-
# The <tt>Application</tt> class adds a couple more paths to this set. And as in your
-
# <tt>Application</tt>, all folders under +app+ are automatically added to the load path.
-
# If you have an <tt>app/observers</tt> folder for example, it will be added by default.
-
#
-
# == Endpoint
-
#
-
# An engine can be also a rack application. It can be useful if you have a rack application that
-
# you would like to wrap with +Engine+ and provide some of the +Engine+'s features.
-
#
-
# To do that, use the +endpoint+ method:
-
#
-
# module MyEngine
-
# class Engine < Rails::Engine
-
# endpoint MyRackApplication
-
# end
-
# end
-
#
-
# Now you can mount your engine in application's routes just like that:
-
#
-
# MyRailsApp::Application.routes.draw do
-
# mount MyEngine::Engine => "/engine"
-
# end
-
#
-
# == Middleware stack
-
#
-
# As an engine can now be a rack endpoint, it can also have a middleware
-
# stack. The usage is exactly the same as in <tt>Application</tt>:
-
#
-
# module MyEngine
-
# class Engine < Rails::Engine
-
# middleware.use SomeMiddleware
-
# end
-
# end
-
#
-
# == Routes
-
#
-
# If you don't specify an endpoint, routes will be used as the default
-
# endpoint. You can use them just like you use an application's routes:
-
#
-
# # ENGINE/config/routes.rb
-
# MyEngine::Engine.routes.draw do
-
# get "/" => "posts#index"
-
# end
-
#
-
# == Mount priority
-
#
-
# Note that now there can be more than one router in your application, and it's better to avoid
-
# passing requests through many routers. Consider this situation:
-
#
-
# MyRailsApp::Application.routes.draw do
-
# mount MyEngine::Engine => "/blog"
-
# get "/blog/omg" => "main#omg"
-
# end
-
#
-
# +MyEngine+ is mounted at <tt>/blog</tt>, and <tt>/blog/omg</tt> points to application's
-
# controller. In such a situation, requests to <tt>/blog/omg</tt> will go through +MyEngine+,
-
# and if there is no such route in +Engine+'s routes, it will be dispatched to <tt>main#omg</tt>.
-
# It's much better to swap that:
-
#
-
# MyRailsApp::Application.routes.draw do
-
# get "/blog/omg" => "main#omg"
-
# mount MyEngine::Engine => "/blog"
-
# end
-
#
-
# Now, +Engine+ will get only requests that were not handled by +Application+.
-
#
-
# == Engine name
-
#
-
# There are some places where an Engine's name is used:
-
#
-
# * routes: when you mount an Engine with <tt>mount(MyEngine::Engine => '/my_engine')</tt>,
-
# it's used as default :as option
-
# * rake task for installing migrations <tt>my_engine:install:migrations</tt>
-
#
-
# Engine name is set by default based on class name. For <tt>MyEngine::Engine</tt> it will be
-
# <tt>my_engine_engine</tt>. You can change it manually using the <tt>engine_name</tt> method:
-
#
-
# module MyEngine
-
# class Engine < Rails::Engine
-
# engine_name "my_engine"
-
# end
-
# end
-
#
-
# == Isolated Engine
-
#
-
# Normally when you create controllers, helpers and models inside an engine, they are treated
-
# as if they were created inside the application itself. This means that all helpers and
-
# named routes from the application will be available to your engine's controllers as well.
-
#
-
# However, sometimes you want to isolate your engine from the application, especially if your engine
-
# has its own router. To do that, you simply need to call +isolate_namespace+. This method requires
-
# you to pass a module where all your controllers, helpers and models should be nested to:
-
#
-
# module MyEngine
-
# class Engine < Rails::Engine
-
# isolate_namespace MyEngine
-
# end
-
# end
-
#
-
# With such an engine, everything that is inside the +MyEngine+ module will be isolated from
-
# the application.
-
#
-
# Consider such controller:
-
#
-
# module MyEngine
-
# class FooController < ActionController::Base
-
# end
-
# end
-
#
-
# If an engine is marked as isolated, +FooController+ has access only to helpers from +Engine+ and
-
# <tt>url_helpers</tt> from <tt>MyEngine::Engine.routes</tt>.
-
#
-
# The next thing that changes in isolated engines is the behavior of routes. Normally, when you namespace
-
# your controllers, you also need to do namespace all your routes. With an isolated engine,
-
# the namespace is applied by default, so you can ignore it in routes:
-
#
-
# MyEngine::Engine.routes.draw do
-
# resources :articles
-
# end
-
#
-
# The routes above will automatically point to <tt>MyEngine::ArticlesController</tt>. Furthermore, you don't
-
# need to use longer url helpers like <tt>my_engine_articles_path</tt>. Instead, you should simply use
-
# <tt>articles_path</tt> as you would do with your application.
-
#
-
# To make that behavior consistent with other parts of the framework, an isolated engine also has influence on
-
# <tt>ActiveModel::Naming</tt>. When you use a namespaced model, like <tt>MyEngine::Article</tt>, it will normally
-
# use the prefix "my_engine". In an isolated engine, the prefix will be omitted in url helpers and
-
# form fields for convenience.
-
#
-
# polymorphic_url(MyEngine::Article.new) # => "articles_path"
-
#
-
# form_for(MyEngine::Article.new) do
-
# text_field :title # => <input type="text" name="article[title]" id="article_title" />
-
# end
-
#
-
# Additionally, an isolated engine will set its name according to namespace, so
-
# MyEngine::Engine.engine_name will be "my_engine". It will also set MyEngine.table_name_prefix
-
# to "my_engine_", changing the MyEngine::Article model to use the my_engine_articles table.
-
#
-
# == Using Engine's routes outside Engine
-
#
-
# Since you can now mount an engine inside application's routes, you do not have direct access to +Engine+'s
-
# <tt>url_helpers</tt> inside +Application+. When you mount an engine in an application's routes, a special helper is
-
# created to allow you to do that. Consider such a scenario:
-
#
-
# # config/routes.rb
-
# MyApplication::Application.routes.draw do
-
# mount MyEngine::Engine => "/my_engine", as: "my_engine"
-
# get "/foo" => "foo#index"
-
# end
-
#
-
# Now, you can use the <tt>my_engine</tt> helper inside your application:
-
#
-
# class FooController < ApplicationController
-
# def index
-
# my_engine.root_url #=> /my_engine/
-
# end
-
# end
-
#
-
# There is also a <tt>main_app</tt> helper that gives you access to application's routes inside Engine:
-
#
-
# module MyEngine
-
# class BarController
-
# def index
-
# main_app.foo_path #=> /foo
-
# end
-
# end
-
# end
-
#
-
# Note that the <tt>:as</tt> option given to mount takes the <tt>engine_name</tt> as default, so most of the time
-
# you can simply omit it.
-
#
-
# Finally, if you want to generate a url to an engine's route using
-
# <tt>polymorphic_url</tt>, you also need to pass the engine helper. Let's
-
# say that you want to create a form pointing to one of the engine's routes.
-
# All you need to do is pass the helper as the first element in array with
-
# attributes for url:
-
#
-
# form_for([my_engine, @user])
-
#
-
# This code will use <tt>my_engine.user_path(@user)</tt> to generate the proper route.
-
#
-
# == Isolated engine's helpers
-
#
-
# Sometimes you may want to isolate engine, but use helpers that are defined for it.
-
# If you want to share just a few specific helpers you can add them to application's
-
# helpers in ApplicationController:
-
#
-
# class ApplicationController < ActionController::Base
-
# helper MyEngine::SharedEngineHelper
-
# end
-
#
-
# If you want to include all of the engine's helpers, you can use #helper method on an engine's
-
# instance:
-
#
-
# class ApplicationController < ActionController::Base
-
# helper MyEngine::Engine.helpers
-
# end
-
#
-
# It will include all of the helpers from engine's directory. Take into account that this does
-
# not include helpers defined in controllers with helper_method or other similar solutions,
-
# only helpers defined in the helpers directory will be included.
-
#
-
# == Migrations & seed data
-
#
-
# Engines can have their own migrations. The default path for migrations is exactly the same
-
# as in application: <tt>db/migrate</tt>
-
#
-
# To use engine's migrations in application you can use rake task, which copies them to
-
# application's dir:
-
#
-
# rake ENGINE_NAME:install:migrations
-
#
-
# Note that some of the migrations may be skipped if a migration with the same name already exists
-
# in application. In such a situation you must decide whether to leave that migration or rename the
-
# migration in the application and rerun copying migrations.
-
#
-
# If your engine has migrations, you may also want to prepare data for the database in
-
# the <tt>db/seeds.rb</tt> file. You can load that data using the <tt>load_seed</tt> method, e.g.
-
#
-
# MyEngine::Engine.load_seed
-
#
-
# == Loading priority
-
#
-
# In order to change engine's priority you can use +config.railties_order+ in main application.
-
# It will affect the priority of loading views, helpers, assets and all the other files
-
# related to engine or application.
-
#
-
# # load Blog::Engine with highest priority, followed by application and other railties
-
# config.railties_order = [Blog::Engine, :main_app, :all]
-
1
class Engine < Railtie
-
1
autoload :Configuration, "rails/engine/configuration"
-
-
1
class << self
-
1
attr_accessor :called_from, :isolated
-
-
1
alias :isolated? :isolated
-
1
alias :engine_name :railtie_name
-
-
1
delegate :eager_load!, to: :instance
-
-
1
def inherited(base)
-
1
unless base.abstract_railtie?
-
1
Rails::Railtie::Configuration.eager_load_namespaces << base
-
-
1
base.called_from = begin
-
# Remove the line number from backtraces making sure we don't leave anything behind
-
18
call_stack = caller.map { |p| p.sub(/:\d+.*/, '') }
-
2
File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] })
-
end
-
end
-
-
1
super
-
end
-
-
1
def endpoint(endpoint = nil)
-
@endpoint ||= nil
-
@endpoint = endpoint if endpoint
-
@endpoint
-
end
-
-
1
def isolate_namespace(mod)
-
engine_name(generate_railtie_name(mod))
-
-
self.routes.default_scope = { module: ActiveSupport::Inflector.underscore(mod.name) }
-
self.isolated = true
-
-
unless mod.respond_to?(:railtie_namespace)
-
name, railtie = engine_name, self
-
-
mod.singleton_class.instance_eval do
-
define_method(:railtie_namespace) { railtie }
-
-
unless mod.respond_to?(:table_name_prefix)
-
define_method(:table_name_prefix) { "#{name}_" }
-
end
-
-
unless mod.respond_to?(:use_relative_model_naming?)
-
class_eval "def use_relative_model_naming?; true; end", __FILE__, __LINE__
-
end
-
-
unless mod.respond_to?(:railtie_helpers_paths)
-
define_method(:railtie_helpers_paths) { railtie.helpers_paths }
-
end
-
-
unless mod.respond_to?(:railtie_routes_url_helpers)
-
define_method(:railtie_routes_url_helpers) { railtie.routes.url_helpers }
-
end
-
end
-
end
-
end
-
-
# Finds engine with given path
-
1
def find(path)
-
expanded_path = File.expand_path path
-
Rails::Engine.subclasses.each do |klass|
-
engine = klass.instance
-
return engine if File.expand_path(engine.root) == expanded_path
-
end
-
nil
-
end
-
end
-
-
1
delegate :middleware, :root, :paths, to: :config
-
1
delegate :engine_name, :isolated?, to: "self.class"
-
-
1
def initialize
-
1
@_all_autoload_paths = nil
-
1
@_all_load_paths = nil
-
1
@app = nil
-
1
@config = nil
-
1
@env_config = nil
-
1
@helpers = nil
-
1
@routes = nil
-
1
super
-
end
-
-
# Load console and invoke the registered hooks.
-
# Check <tt>Rails::Railtie.console</tt> for more info.
-
1
def load_console(app=self)
-
require "pp"
-
require "rails/console/app"
-
require "rails/console/helpers"
-
run_console_blocks(app)
-
self
-
end
-
-
# Load Rails runner and invoke the registered hooks.
-
# Check <tt>Rails::Railtie.runner</tt> for more info.
-
1
def load_runner(app=self)
-
run_runner_blocks(app)
-
self
-
end
-
-
# Load Rake, railties tasks and invoke the registered hooks.
-
# Check <tt>Rails::Railtie.rake_tasks</tt> for more info.
-
1
def load_tasks(app=self)
-
require "rake"
-
run_tasks_blocks(app)
-
self
-
end
-
-
# Load rails generators and invoke the registered hooks.
-
# Check <tt>Rails::Railtie.generators</tt> for more info.
-
1
def load_generators(app=self)
-
require "rails/generators"
-
run_generators_blocks(app)
-
Rails::Generators.configure!(app.config.generators)
-
self
-
end
-
-
# Eager load the application by loading all ruby
-
# files inside eager_load paths.
-
1
def eager_load!
-
config.eager_load_paths.each do |load_path|
-
matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/
-
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
-
require_dependency file.sub(matcher, '\1')
-
end
-
end
-
end
-
-
# Returns a module with all the helpers defined for the engine.
-
1
def helpers
-
@helpers ||= begin
-
helpers = Module.new
-
all = ActionController::Base.all_helpers_from_path(helpers_paths)
-
ActionController::Base.modules_for_helpers(all).each do |mod|
-
helpers.send(:include, mod)
-
end
-
helpers
-
end
-
end
-
-
# Returns all registered helpers paths.
-
1
def helpers_paths
-
paths["app/helpers"].existent
-
end
-
-
# Returns the underlying rack application for this engine.
-
1
def app
-
@app ||= begin
-
config.middleware = config.middleware.merge_into(default_middleware_stack)
-
config.middleware.build(endpoint)
-
end
-
end
-
-
# Returns the endpoint for this engine. If none is registered,
-
# defaults to an ActionDispatch::Routing::RouteSet.
-
1
def endpoint
-
self.class.endpoint || routes
-
end
-
-
# Define the Rack API for this engine.
-
1
def call(env)
-
env.merge!(env_config)
-
if env['SCRIPT_NAME']
-
env.merge! "ROUTES_#{routes.object_id}_SCRIPT_NAME" => env['SCRIPT_NAME'].dup
-
end
-
app.call(env)
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
1
def env_config
-
@env_config ||= {
-
'action_dispatch.routes' => routes
-
}
-
end
-
-
# Defines the routes for this engine. If a block is given to
-
# routes, it is appended to the engine.
-
1
def routes
-
5
@routes ||= ActionDispatch::Routing::RouteSet.new
-
5
@routes.append(&Proc.new) if block_given?
-
5
@routes
-
end
-
-
# Define the configuration object for the engine.
-
1
def config
-
@config ||= Engine::Configuration.new(find_root_with_flag("lib"))
-
end
-
-
# Load data from db/seeds.rb file. It can be used in to load engines'
-
# seeds, e.g.:
-
#
-
# Blog::Engine.load_seed
-
1
def load_seed
-
seed_file = paths["db/seeds.rb"].existent.first
-
load(seed_file) if seed_file
-
end
-
-
# Add configured load paths to ruby load paths and remove duplicates.
-
1
initializer :set_load_path, before: :bootstrap_hook do
-
_all_load_paths.reverse_each do |path|
-
$LOAD_PATH.unshift(path) if File.directory?(path)
-
end
-
$LOAD_PATH.uniq!
-
end
-
-
# Set the paths from which Rails will automatically load source files,
-
# and the load_once paths.
-
#
-
# This needs to be an initializer, since it needs to run once
-
# per engine and get the engine as a block parameter
-
1
initializer :set_autoload_paths, before: :bootstrap_hook do |app|
-
ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
-
ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths)
-
-
# Freeze so future modifications will fail rather than do nothing mysteriously
-
config.autoload_paths.freeze
-
config.eager_load_paths.freeze
-
config.autoload_once_paths.freeze
-
end
-
-
1
initializer :add_routing_paths do |app|
-
paths = self.paths["config/routes.rb"].existent
-
-
if routes? || paths.any?
-
app.routes_reloader.paths.unshift(*paths)
-
app.routes_reloader.route_sets << routes
-
end
-
end
-
-
# I18n load paths are a special case since the ones added
-
# later have higher priority.
-
1
initializer :add_locales do
-
config.i18n.railties_load_path.concat(paths["config/locales"].existent)
-
end
-
-
1
initializer :add_view_paths do
-
views = paths["app/views"].existent
-
unless views.empty?
-
ActiveSupport.on_load(:action_controller){ prepend_view_path(views) if respond_to?(:prepend_view_path) }
-
ActiveSupport.on_load(:action_mailer){ prepend_view_path(views) }
-
end
-
end
-
-
1
initializer :load_environment_config, before: :load_environment_hook, group: :all do
-
paths["config/environments"].existent.each do |environment|
-
require environment
-
end
-
end
-
-
1
initializer :append_assets_path, group: :all do |app|
-
app.config.assets.paths.unshift(*paths["vendor/assets"].existent_directories)
-
app.config.assets.paths.unshift(*paths["lib/assets"].existent_directories)
-
app.config.assets.paths.unshift(*paths["app/assets"].existent_directories)
-
end
-
-
1
initializer :prepend_helpers_path do |app|
-
if !isolated? || (app == self)
-
app.config.helpers_paths.unshift(*paths["app/helpers"].existent)
-
end
-
end
-
-
1
initializer :load_config_initializers do
-
config.paths["config/initializers"].existent.sort.each do |initializer|
-
load(initializer)
-
end
-
end
-
-
1
initializer :engines_blank_point do
-
# We need this initializer so all extra initializers added in engines are
-
# consistently executed after all the initializers above across all engines.
-
end
-
-
1
rake_tasks do
-
next if self.is_a?(Rails::Application)
-
next unless has_migrations?
-
-
namespace railtie_name do
-
namespace :install do
-
desc "Copy migrations from #{railtie_name} to application"
-
task :migrations do
-
ENV["FROM"] = railtie_name
-
if Rake::Task.task_defined?("railties:install:migrations")
-
Rake::Task["railties:install:migrations"].invoke
-
else
-
Rake::Task["app:railties:install:migrations"].invoke
-
end
-
end
-
end
-
end
-
end
-
-
1
protected
-
-
1
def run_tasks_blocks(*) #:nodoc:
-
super
-
paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
-
end
-
-
1
def routes? #:nodoc:
-
@routes
-
end
-
-
1
def has_migrations? #:nodoc:
-
paths["db/migrate"].existent.any?
-
end
-
-
1
def find_root_with_flag(flag, default=nil) #:nodoc:
-
root_path = self.class.called_from
-
-
while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}")
-
parent = File.dirname(root_path)
-
root_path = parent != root_path && parent
-
end
-
-
root = File.exist?("#{root_path}/#{flag}") ? root_path : default
-
raise "Could not find root path for #{self}" unless root
-
-
Pathname.new File.realpath root
-
end
-
-
1
def default_middleware_stack #:nodoc:
-
ActionDispatch::MiddlewareStack.new
-
end
-
-
1
def _all_autoload_once_paths #:nodoc:
-
config.autoload_once_paths
-
end
-
-
1
def _all_autoload_paths #:nodoc:
-
@_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq
-
end
-
-
1
def _all_load_paths #:nodoc:
-
@_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq
-
end
-
end
-
end
-
1
require 'tsort'
-
-
1
module Rails
-
1
module Initializable
-
1
def self.included(base) #:nodoc:
-
1
base.extend ClassMethods
-
end
-
-
1
class Initializer
-
1
attr_reader :name, :block
-
-
1
def initialize(name, context, options, &block)
-
10
options[:group] ||= :default
-
10
@name, @context, @options, @block = name, context, options, block
-
end
-
-
1
def before
-
@options[:before]
-
end
-
-
1
def after
-
@options[:after]
-
end
-
-
1
def belongs_to?(group)
-
@options[:group] == group || @options[:group] == :all
-
end
-
-
1
def run(*args)
-
@context.instance_exec(*args, &block)
-
end
-
-
1
def bind(context)
-
return self if @context
-
Initializer.new(@name, context, @options, &block)
-
end
-
end
-
-
1
class Collection < Array
-
1
include TSort
-
-
1
alias :tsort_each_node :each
-
1
def tsort_each_child(initializer, &block)
-
select { |i| i.before == initializer.name || i.name == initializer.after }.each(&block)
-
end
-
-
1
def +(other)
-
Collection.new(to_a + other.to_a)
-
end
-
end
-
-
1
def run_initializers(group=:default, *args)
-
return if instance_variable_defined?(:@ran)
-
initializers.tsort_each do |initializer|
-
initializer.run(*args) if initializer.belongs_to?(group)
-
end
-
@ran = true
-
end
-
-
1
def initializers
-
@initializers ||= self.class.initializers_for(self)
-
end
-
-
1
module ClassMethods
-
1
def initializers
-
38
@initializers ||= Collection.new
-
end
-
-
1
def initializers_chain
-
initializers = Collection.new
-
ancestors.reverse_each do |klass|
-
next unless klass.respond_to?(:initializers)
-
initializers = initializers + klass.initializers
-
end
-
initializers
-
end
-
-
1
def initializers_for(binding)
-
Collection.new(initializers_chain.map { |i| i.bind(binding) })
-
end
-
-
1
def initializer(name, opts = {}, &blk)
-
10
raise ArgumentError, "A block must be passed when defining an initializer" unless blk
-
55
opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
-
10
initializers << Initializer.new(name, nil, opts, &blk)
-
end
-
end
-
end
-
end
-
1
module Rails
-
1
module Paths
-
# This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system.
-
# It allows you to collect information about how you want to structure your application
-
# paths by a Hash like API. It requires you to give a physical path on initialization.
-
#
-
# root = Root.new "/rails"
-
# root.add "app/controllers", eager_load: true
-
#
-
# The command above creates a new root object and add "app/controllers" as a path.
-
# This means we can get a <tt>Rails::Paths::Path</tt> object back like below:
-
#
-
# path = root["app/controllers"]
-
# path.eager_load? # => true
-
# path.is_a?(Rails::Paths::Path) # => true
-
#
-
# The +Path+ object is simply an enumerable and allows you to easily add extra paths:
-
#
-
# path.is_a?(Enumerable) # => true
-
# path.to_ary.inspect # => ["app/controllers"]
-
#
-
# path << "lib/controllers"
-
# path.to_ary.inspect # => ["app/controllers", "lib/controllers"]
-
#
-
# Notice that when you add a path using +add+, the path object created already
-
# contains the path with the same path value given to +add+. In some situations,
-
# you may not want this behavior, so you can give :with as option.
-
#
-
# root.add "config/routes", with: "config/routes.rb"
-
# root["config/routes"].inspect # => ["config/routes.rb"]
-
#
-
# The +add+ method accepts the following options as arguments:
-
# eager_load, autoload, autoload_once and glob.
-
#
-
# Finally, the +Path+ object also provides a few helpers:
-
#
-
# root = Root.new "/rails"
-
# root.add "app/controllers"
-
#
-
# root["app/controllers"].expanded # => ["/rails/app/controllers"]
-
# root["app/controllers"].existent # => ["/rails/app/controllers"]
-
#
-
# Check the <tt>Rails::Paths::Path</tt> documentation for more information.
-
1
class Root
-
1
attr_accessor :path
-
-
1
def initialize(path)
-
@current = nil
-
@path = path
-
@root = {}
-
end
-
-
1
def []=(path, value)
-
glob = self[path] ? self[path].glob : nil
-
add(path, with: value, glob: glob)
-
end
-
-
1
def add(path, options={})
-
with = options[:with] || path
-
@root[path] = Path.new(self, path, [with].flatten, options)
-
end
-
-
1
def [](path)
-
@root[path]
-
end
-
-
1
def values
-
@root.values
-
end
-
-
1
def keys
-
@root.keys
-
end
-
-
1
def values_at(*list)
-
@root.values_at(*list)
-
end
-
-
1
def all_paths
-
values.tap { |v| v.uniq! }
-
end
-
-
1
def autoload_once
-
filter_by(:autoload_once?)
-
end
-
-
1
def eager_load
-
filter_by(:eager_load?)
-
end
-
-
1
def autoload_paths
-
filter_by(:autoload?)
-
end
-
-
1
def load_paths
-
filter_by(:load_path?)
-
end
-
-
1
protected
-
-
1
def filter_by(constraint)
-
yes = []
-
no = []
-
-
all_paths.each do |path|
-
paths = path.existent + path.existent_base_paths
-
path.send(constraint) ? yes.concat(paths) : no.concat(paths)
-
end
-
-
all = yes - no
-
all.uniq!
-
all
-
end
-
end
-
-
1
class Path
-
1
include Enumerable
-
-
1
attr_accessor :glob
-
-
1
def initialize(root, current, paths, options = {})
-
@paths = paths
-
@current = current
-
@root = root
-
@glob = options[:glob]
-
-
options[:autoload_once] ? autoload_once! : skip_autoload_once!
-
options[:eager_load] ? eager_load! : skip_eager_load!
-
options[:autoload] ? autoload! : skip_autoload!
-
options[:load_path] ? load_path! : skip_load_path!
-
end
-
-
1
def children
-
keys = @root.keys.select { |k| k.include?(@current) }
-
keys.delete(@current)
-
@root.values_at(*keys.sort)
-
end
-
1
deprecate :children
-
-
1
def first
-
expanded.first
-
end
-
-
1
def last
-
expanded.last
-
end
-
-
1
%w(autoload_once eager_load autoload load_path).each do |m|
-
4
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{m}! # def eager_load!
-
@#{m} = true # @eager_load = true
-
end # end
-
#
-
def skip_#{m}! # def skip_eager_load!
-
@#{m} = false # @eager_load = false
-
end # end
-
#
-
def #{m}? # def eager_load?
-
@#{m} # @eager_load
-
end # end
-
RUBY
-
end
-
-
1
def each(&block)
-
@paths.each(&block)
-
end
-
-
1
def <<(path)
-
@paths << path
-
end
-
1
alias :push :<<
-
-
1
def concat(paths)
-
@paths.concat paths
-
end
-
-
1
def unshift(path)
-
@paths.unshift path
-
end
-
-
1
def to_ary
-
@paths
-
end
-
-
# Expands all paths against the root and return all unique values.
-
1
def expanded
-
raise "You need to set a path root" unless @root.path
-
result = []
-
-
each do |p|
-
path = File.expand_path(p, @root.path)
-
-
if @glob && File.directory?(path)
-
result.concat Dir.chdir(path) {
-
Dir.glob(@glob).map { |file| File.join path, file }.sort
-
}
-
else
-
result << path
-
end
-
end
-
-
result.uniq!
-
result
-
end
-
-
# Returns all expanded paths but only if they exist in the filesystem.
-
1
def existent
-
expanded.select { |f| File.exists?(f) }
-
end
-
-
1
def existent_directories
-
expanded.select { |d| File.directory?(d) }
-
end
-
-
1
def existent_base_paths
-
map { |p| File.expand_path(p, @root.path) }.select{ |f| File.exist? f }
-
end
-
-
1
alias to_a expanded
-
end
-
end
-
end
-
1
module Rails
-
1
module Rack
-
1
autoload :Debugger, "rails/rack/debugger"
-
1
autoload :Logger, "rails/rack/logger"
-
1
autoload :LogTailer, "rails/rack/log_tailer"
-
end
-
end
-
1
require 'rails/initializable'
-
1
require 'rails/configuration'
-
1
require 'active_support/inflector'
-
1
require 'active_support/core_ext/module/introspection'
-
1
require 'active_support/core_ext/module/delegation'
-
-
1
module Rails
-
# Railtie is the core of the Rails framework and provides several hooks to extend
-
# Rails and/or modify the initialization process.
-
#
-
# Every major component of Rails (Action Mailer, Action Controller,
-
# Action View and Active Record) is a Railtie. Each of
-
# them is responsible for their own initialization. This makes Rails itself
-
# absent of any component hooks, allowing other components to be used in
-
# place of any of the Rails defaults.
-
#
-
# Developing a Rails extension does _not_ require any implementation of
-
# Railtie, but if you need to interact with the Rails framework during
-
# or after boot, then Railtie is needed.
-
#
-
# For example, an extension doing any of the following would require Railtie:
-
#
-
# * creating initializers
-
# * configuring a Rails framework for the application, like setting a generator
-
# * +adding config.*+ keys to the environment
-
# * setting up a subscriber with ActiveSupport::Notifications
-
# * adding rake tasks
-
#
-
# == Creating your Railtie
-
#
-
# To extend Rails using Railtie, create a Railtie class which inherits
-
# from Rails::Railtie within your extension's namespace. This class must be
-
# loaded during the Rails boot process.
-
#
-
# The following example demonstrates an extension which can be used with or without Rails.
-
#
-
# # lib/my_gem/railtie.rb
-
# module MyGem
-
# class Railtie < Rails::Railtie
-
# end
-
# end
-
#
-
# # lib/my_gem.rb
-
# require 'my_gem/railtie' if defined?(Rails)
-
#
-
# == Initializers
-
#
-
# To add an initialization step from your Railtie to Rails boot process, you just need
-
# to create an initializer block:
-
#
-
# class MyRailtie < Rails::Railtie
-
# initializer "my_railtie.configure_rails_initialization" do
-
# # some initialization behavior
-
# end
-
# end
-
#
-
# If specified, the block can also receive the application object, in case you
-
# need to access some application specific configuration, like middleware:
-
#
-
# class MyRailtie < Rails::Railtie
-
# initializer "my_railtie.configure_rails_initialization" do |app|
-
# app.middleware.use MyRailtie::Middleware
-
# end
-
# end
-
#
-
# Finally, you can also pass :before and :after as option to initializer, in case
-
# you want to couple it with a specific step in the initialization process.
-
#
-
# == Configuration
-
#
-
# Inside the Railtie class, you can access a config object which contains configuration
-
# shared by all railties and the application:
-
#
-
# class MyRailtie < Rails::Railtie
-
# # Customize the ORM
-
# config.app_generators.orm :my_railtie_orm
-
#
-
# # Add a to_prepare block which is executed once in production
-
# # and before each request in development
-
# config.to_prepare do
-
# MyRailtie.setup!
-
# end
-
# end
-
#
-
# == Loading rake tasks and generators
-
#
-
# If your railtie has rake tasks, you can tell Rails to load them through the method
-
# rake_tasks:
-
#
-
# class MyRailtie < Rails::Railtie
-
# rake_tasks do
-
# load "path/to/my_railtie.tasks"
-
# end
-
# end
-
#
-
# By default, Rails load generators from your load path. However, if you want to place
-
# your generators at a different location, you can specify in your Railtie a block which
-
# will load them during normal generators lookup:
-
#
-
# class MyRailtie < Rails::Railtie
-
# generators do
-
# require "path/to/my_railtie_generator"
-
# end
-
# end
-
#
-
# == Application and Engine
-
#
-
# A Rails::Engine is nothing more than a Railtie with some initializers already set.
-
# And since Rails::Application is an engine, the same configuration described here
-
# can be used in both.
-
#
-
# Be sure to look at the documentation of those specific classes for more information.
-
#
-
1
class Railtie
-
1
autoload :Configurable, "rails/railtie/configurable"
-
1
autoload :Configuration, "rails/railtie/configuration"
-
-
1
include Initializable
-
-
1
ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Engine Rails::Application)
-
-
1
class << self
-
1
private :new
-
-
1
def subclasses
-
2
@subclasses ||= []
-
end
-
-
1
def inherited(base)
-
3
unless base.abstract_railtie?
-
2
base.send(:include, Railtie::Configurable)
-
2
subclasses << base
-
end
-
end
-
-
1
def rake_tasks(&blk)
-
1
@rake_tasks ||= []
-
1
@rake_tasks << blk if blk
-
1
@rake_tasks
-
end
-
-
1
def console(&blk)
-
@load_console ||= []
-
@load_console << blk if blk
-
@load_console
-
end
-
-
1
def runner(&blk)
-
@load_runner ||= []
-
@load_runner << blk if blk
-
@load_runner
-
end
-
-
1
def generators(&blk)
-
@generators ||= []
-
@generators << blk if blk
-
@generators
-
end
-
-
1
def abstract_railtie?
-
4
ABSTRACT_RAILTIES.include?(name)
-
end
-
-
1
def railtie_name(name = nil)
-
@railtie_name = name.to_s if name
-
@railtie_name ||= generate_railtie_name(self.name)
-
end
-
-
1
protected
-
1
def generate_railtie_name(class_or_module)
-
ActiveSupport::Inflector.underscore(class_or_module).tr("/", "_")
-
end
-
end
-
-
1
delegate :railtie_name, to: "self.class"
-
-
1
def config
-
1
@config ||= Railtie::Configuration.new
-
end
-
-
1
def railtie_namespace
-
@railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) }
-
end
-
-
1
protected
-
-
1
def run_console_blocks(app) #:nodoc:
-
self.class.console.each { |block| block.call(app) }
-
end
-
-
1
def run_generators_blocks(app) #:nodoc:
-
self.class.generators.each { |block| block.call(app) }
-
end
-
-
1
def run_runner_blocks(app) #:nodoc:
-
self.class.runner.each { |block| block.call(app) }
-
end
-
-
1
def run_tasks_blocks(app) #:nodoc:
-
extend Rake::DSL
-
self.class.rake_tasks.each { |block| instance_exec(app, &block) }
-
-
# Load also tasks from all superclasses
-
klass = self.class.superclass
-
-
while klass.respond_to?(:rake_tasks)
-
klass.rake_tasks.each { |t| instance_exec(app, &t) }
-
klass = klass.superclass
-
end
-
end
-
end
-
end
-
1
require 'active_support/concern'
-
-
1
module Rails
-
1
class Railtie
-
1
module Configurable
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
delegate :config, to: :instance
-
-
1
def inherited(base)
-
raise "You cannot inherit from a #{self.superclass.name} child"
-
end
-
-
1
def instance
-
11
@instance ||= new
-
end
-
-
1
def respond_to?(*args)
-
5
super || instance.respond_to?(*args)
-
end
-
-
1
def configure(&block)
-
class_eval(&block)
-
end
-
-
1
protected
-
-
1
def method_missing(*args, &block)
-
5
instance.send(*args, &block)
-
end
-
end
-
end
-
end
-
end
-
1
require 'rails/configuration'
-
-
1
module Rails
-
1
class Railtie
-
1
class Configuration
-
1
def initialize
-
1
@@options ||= {}
-
end
-
-
# Expose the eager_load_namespaces at "module" level for convenience.
-
1
def self.eager_load_namespaces #:nodoc:
-
1
@@eager_load_namespaces ||= []
-
end
-
-
# All namespaces that are eager loaded
-
1
def eager_load_namespaces
-
@@eager_load_namespaces ||= []
-
end
-
-
# Add files that should be watched for change.
-
1
def watchable_files
-
@@watchable_files ||= []
-
end
-
-
# Add directories that should be watched for change.
-
# The key of the hashes should be directories and the values should
-
# be an array of extensions to match in each directory.
-
1
def watchable_dirs
-
@@watchable_dirs ||= {}
-
end
-
-
# This allows you to modify the application's middlewares from Engines.
-
#
-
# All operations you run on the app_middleware will be replayed on the
-
# application once it is defined and the default_middlewares are
-
# created
-
1
def app_middleware
-
@@app_middleware ||= Rails::Configuration::MiddlewareStackProxy.new
-
end
-
-
# This allows you to modify application's generators from Railties.
-
#
-
# Values set on app_generators will become defaults for application, unless
-
# application overwrites them.
-
1
def app_generators
-
@@app_generators ||= Rails::Configuration::Generators.new
-
yield(@@app_generators) if block_given?
-
@@app_generators
-
end
-
-
# First configurable block to run. Called before any initializers are run.
-
1
def before_configuration(&block)
-
1
ActiveSupport.on_load(:before_configuration, yield: true, &block)
-
end
-
-
# Third configurable block to run. Does not run if +config.cache_classes+
-
# set to false.
-
1
def before_eager_load(&block)
-
ActiveSupport.on_load(:before_eager_load, yield: true, &block)
-
end
-
-
# Second configurable block to run. Called before frameworks initialize.
-
1
def before_initialize(&block)
-
ActiveSupport.on_load(:before_initialize, yield: true, &block)
-
end
-
-
# Last configurable block to run. Called after frameworks initialize.
-
1
def after_initialize(&block)
-
ActiveSupport.on_load(:after_initialize, yield: true, &block)
-
end
-
-
# Array of callbacks defined by #to_prepare.
-
1
def to_prepare_blocks
-
@@to_prepare_blocks ||= []
-
end
-
-
# Defines generic callbacks to run before #after_initialize. Useful for
-
# Rails::Railtie subclasses.
-
1
def to_prepare(&blk)
-
to_prepare_blocks << blk if blk
-
end
-
-
1
def respond_to?(name)
-
super || @@options.key?(name.to_sym)
-
end
-
-
1
private
-
-
1
def method_missing(name, *args, &blk)
-
if name.to_s =~ /=$/
-
@@options[$`.to_sym] = args.first
-
elsif @@options.key?(name)
-
@@options[name]
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
#!/usr/bin/env ruby
-
-
#--
-
# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
-
# All rights reserved.
-
-
# Permission is granted for use, copying, modification, distribution,
-
# and distribution of modified versions of this work as long as the
-
# above copyright notice is included.
-
#++
-
-
1
require 'builder/xmlmarkup'
-
1
require 'builder/xmlevents'
-
#!/usr/bin/env ruby
-
#--
-
# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
-
# All rights reserved.
-
-
# Permission is granted for use, copying, modification, distribution,
-
# and distribution of modified versions of this work as long as the
-
# above copyright notice is included.
-
#++
-
-
######################################################################
-
# BlankSlate has been promoted to a top level name and is now
-
# available as a standalone gem. We make the name available in the
-
# Builder namespace for compatibility.
-
#
-
1
module Builder
-
1
if Object::const_defined?(:BasicObject)
-
1
BlankSlate = ::BasicObject
-
else
-
require 'blankslate'
-
BlankSlate = ::BlankSlate
-
end
-
end
-
#!/usr/bin/env ruby
-
-
# The XChar library is provided courtesy of Sam Ruby (See
-
# http://intertwingly.net/stories/2005/09/28/xchar.rb)
-
-
# --------------------------------------------------------------------
-
-
# If the Builder::XChar module is not currently defined, fail on any
-
# name clashes in standard library classes.
-
-
1
module Builder
-
1
def self.check_for_name_collision(klass, method_name, defined_constant=nil)
-
if klass.method_defined?(method_name.to_s)
-
fail RuntimeError,
-
"Name Collision: Method '#{method_name}' is already defined in #{klass}"
-
end
-
end
-
end
-
-
1
if ! defined?(Builder::XChar) and ! String.method_defined?(:encode)
-
Builder.check_for_name_collision(String, "to_xs")
-
Builder.check_for_name_collision(Fixnum, "xchr")
-
end
-
-
######################################################################
-
1
module Builder
-
-
####################################################################
-
# XML Character converter, from Sam Ruby:
-
# (see http://intertwingly.net/stories/2005/09/28/xchar.rb).
-
#
-
1
module XChar # :nodoc:
-
-
# See
-
# http://intertwingly.net/stories/2004/04/14/i18n.html#CleaningWindows
-
# for details.
-
1
CP1252 = { # :nodoc:
-
128 => 8364, # euro sign
-
130 => 8218, # single low-9 quotation mark
-
131 => 402, # latin small letter f with hook
-
132 => 8222, # double low-9 quotation mark
-
133 => 8230, # horizontal ellipsis
-
134 => 8224, # dagger
-
135 => 8225, # double dagger
-
136 => 710, # modifier letter circumflex accent
-
137 => 8240, # per mille sign
-
138 => 352, # latin capital letter s with caron
-
139 => 8249, # single left-pointing angle quotation mark
-
140 => 338, # latin capital ligature oe
-
142 => 381, # latin capital letter z with caron
-
145 => 8216, # left single quotation mark
-
146 => 8217, # right single quotation mark
-
147 => 8220, # left double quotation mark
-
148 => 8221, # right double quotation mark
-
149 => 8226, # bullet
-
150 => 8211, # en dash
-
151 => 8212, # em dash
-
152 => 732, # small tilde
-
153 => 8482, # trade mark sign
-
154 => 353, # latin small letter s with caron
-
155 => 8250, # single right-pointing angle quotation mark
-
156 => 339, # latin small ligature oe
-
158 => 382, # latin small letter z with caron
-
159 => 376, # latin capital letter y with diaeresis
-
}
-
-
# See http://www.w3.org/TR/REC-xml/#dt-chardata for details.
-
1
PREDEFINED = {
-
38 => '&', # ampersand
-
60 => '<', # left angle bracket
-
62 => '>', # right angle bracket
-
}
-
-
# See http://www.w3.org/TR/REC-xml/#charsets for details.
-
1
VALID = [
-
0x9, 0xA, 0xD,
-
(0x20..0xD7FF),
-
(0xE000..0xFFFD),
-
(0x10000..0x10FFFF)
-
]
-
-
# http://www.fileformat.info/info/unicode/char/fffd/index.htm
-
1
REPLACEMENT_CHAR =
-
if String.method_defined?(:encode)
-
1
"\uFFFD"
-
elsif $KCODE == 'UTF8'
-
"\xEF\xBF\xBD"
-
else
-
'*'
-
end
-
end
-
-
end
-
-
-
1
if String.method_defined?(:encode)
-
1
module Builder
-
1
module XChar # :nodoc:
-
1
CP1252_DIFFERENCES, UNICODE_EQUIVALENT = Builder::XChar::CP1252.each.
-
inject([[],[]]) {|(domain,range),(key,value)|
-
27
[domain << key,range << value]
-
2
}.map {|seq| seq.pack('U*').force_encoding('utf-8')}
-
-
1
XML_PREDEFINED = Regexp.new('[' +
-
Builder::XChar::PREDEFINED.keys.pack('U*').force_encoding('utf-8') +
-
']')
-
-
1
INVALID_XML_CHAR = Regexp.new('[^'+
-
Builder::XChar::VALID.map { |item|
-
6
case item
-
when Fixnum
-
3
[item].pack('U').force_encoding('utf-8')
-
when Range
-
3
[item.first, '-'.ord, item.last].pack('UUU').force_encoding('utf-8')
-
end
-
}.join +
-
']')
-
-
1
ENCODING_BINARY = Encoding.find('BINARY')
-
1
ENCODING_UTF8 = Encoding.find('UTF-8')
-
1
ENCODING_ISO1 = Encoding.find('ISO-8859-1')
-
-
# convert a string to valid UTF-8, compensating for a number of
-
# common errors.
-
1
def XChar.unicode(string)
-
1707
if string.encoding == ENCODING_BINARY
-
if string.ascii_only?
-
string
-
else
-
string = string.clone.force_encoding(ENCODING_UTF8)
-
if string.valid_encoding?
-
string
-
else
-
string.encode(ENCODING_UTF8, ENCODING_ISO1)
-
end
-
end
-
-
1707
elsif string.encoding == ENCODING_UTF8
-
46
if string.valid_encoding?
-
46
string
-
else
-
string.encode(ENCODING_UTF8, ENCODING_ISO1)
-
end
-
-
else
-
1661
string.encode(ENCODING_UTF8)
-
end
-
end
-
-
# encode a string per XML rules
-
1
def XChar.encode(string)
-
1707
unicode(string).
-
tr(CP1252_DIFFERENCES, UNICODE_EQUIVALENT).
-
gsub(INVALID_XML_CHAR, REPLACEMENT_CHAR).
-
60
gsub(XML_PREDEFINED) {|c| PREDEFINED[c.ord]}
-
end
-
end
-
end
-
-
else
-
-
######################################################################
-
# Enhance the Fixnum class with a XML escaped character conversion.
-
#
-
class Fixnum
-
XChar = Builder::XChar if ! defined?(XChar)
-
-
# XML escaped version of chr. When <tt>escape</tt> is set to false
-
# the CP1252 fix is still applied but utf-8 characters are not
-
# converted to character entities.
-
def xchr(escape=true)
-
n = XChar::CP1252[self] || self
-
case n when *XChar::VALID
-
XChar::PREDEFINED[n] or
-
(n<128 ? n.chr : (escape ? "&##{n};" : [n].pack('U*')))
-
else
-
Builder::XChar::REPLACEMENT_CHAR
-
end
-
end
-
end
-
-
-
######################################################################
-
# Enhance the String class with a XML escaped character version of
-
# to_s.
-
#
-
class String
-
# XML escaped version of to_s. When <tt>escape</tt> is set to false
-
# the CP1252 fix is still applied but utf-8 characters are not
-
# converted to character entities.
-
def to_xs(escape=true)
-
unpack('U*').map {|n| n.xchr(escape)}.join # ASCII, UTF-8
-
rescue
-
unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252
-
end
-
end
-
end
-
#!/usr/bin/env ruby
-
-
1
require 'builder/blankslate'
-
-
1
module Builder
-
-
# Generic error for builder
-
1
class IllegalBlockError < RuntimeError; end
-
-
# XmlBase is a base class for building XML builders. See
-
# Builder::XmlMarkup and Builder::XmlEvents for examples.
-
1
class XmlBase < BlankSlate
-
-
1
class << self
-
1
attr_accessor :cache_method_calls
-
end
-
-
# Create an XML markup builder.
-
#
-
# out:: Object receiving the markup. +out+ must respond to
-
# <tt><<</tt>.
-
# indent:: Number of spaces used for indentation (0 implies no
-
# indentation and no line breaks).
-
# initial:: Level of initial indentation.
-
# encoding:: When <tt>encoding</tt> and $KCODE are set to 'utf-8'
-
# characters aren't converted to character entities in
-
# the output stream.
-
1
def initialize(indent=0, initial=0, encoding='utf-8')
-
67
@indent = indent
-
67
@level = initial
-
67
@encoding = encoding.downcase
-
end
-
-
# Create a tag named +sym+. Other than the first argument which
-
# is the tag name, the arguments are the same as the tags
-
# implemented via <tt>method_missing</tt>.
-
1
def tag!(sym, *args, &block)
-
474
text = nil
-
474
attrs = nil
-
474
sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol)
-
474
sym = sym.to_sym unless sym.class == ::Symbol
-
474
args.each do |arg|
-
418
case arg
-
when ::Hash
-
133
attrs ||= {}
-
133
attrs.merge!(arg)
-
when nil
-
# do nothing
-
else
-
285
text ||= ''
-
285
text << arg.to_s
-
end
-
end
-
474
if block
-
124
unless text.nil?
-
::Kernel::raise ::ArgumentError,
-
"XmlMarkup cannot mix a text argument with a block"
-
end
-
124
_indent
-
124
_start_tag(sym, attrs)
-
124
_newline
-
124
begin
-
124
_nested_structures(block)
-
ensure
-
124
_indent
-
124
_end_tag(sym)
-
124
_newline
-
end
-
elsif text.nil?
-
65
_indent
-
65
_start_tag(sym, attrs, true)
-
65
_newline
-
else
-
285
_indent
-
285
_start_tag(sym, attrs)
-
285
text! text
-
285
_end_tag(sym)
-
285
_newline
-
end
-
474
@target
-
end
-
-
# Create XML markup based on the name of the method. This method
-
# is never invoked directly, but is called for each markup method
-
# in the markup block that isn't cached.
-
1
def method_missing(sym, *args, &block)
-
213
cache_method_call(sym) if ::Builder::XmlBase.cache_method_calls
-
213
tag!(sym, *args, &block)
-
end
-
-
# Append text to the output target. Escape any markup. May be
-
# used within the markup brackets as:
-
#
-
# builder.p { |b| b.br; b.text! "HI" } #=> <p><br/>HI</p>
-
1
def text!(text)
-
1354
_text(_escape(text))
-
end
-
-
# Append text to the output target without escaping any markup.
-
# May be used within the markup brackets as:
-
#
-
# builder.p { |x| x << "<br/>HI" } #=> <p><br/>HI</p>
-
#
-
# This is useful when using non-builder enabled software that
-
# generates strings. Just insert the string directly into the
-
# builder without changing the inserted markup.
-
#
-
# It is also useful for stacking builder objects. Builders only
-
# use <tt><<</tt> to append to the target, so by supporting this
-
# method/operation builders can use other builders as their
-
# targets.
-
1
def <<(text)
-
9
_text(text)
-
end
-
-
# For some reason, nil? is sent to the XmlMarkup object. If nil?
-
# is not defined and method_missing is invoked, some strange kind
-
# of recursion happens. Since nil? won't ever be an XML tag, it
-
# is pretty safe to define it here. (Note: this is an example of
-
# cargo cult programming,
-
# cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming).
-
1
def nil?
-
false
-
end
-
-
1
private
-
-
1
require 'builder/xchar'
-
1
if ::String.method_defined?(:encode)
-
1
def _escape(text)
-
1707
result = XChar.encode(text)
-
1707
begin
-
1707
encoding = ::Encoding::find(@encoding)
-
1707
raise Exception if encoding.dummy?
-
1707
result.encode(encoding)
-
rescue
-
# if the encoding can't be supported, use numeric character references
-
result.
-
gsub(/[^\u0000-\u007F]/) {|c| "&##{c.ord};"}.
-
force_encoding('ascii')
-
end
-
end
-
else
-
def _escape(text)
-
if (text.method(:to_xs).arity == 0)
-
text.to_xs
-
else
-
text.to_xs((@encoding != 'utf-8' or $KCODE != 'UTF8'))
-
end
-
end
-
end
-
-
1
def _escape_attribute(text)
-
353
_escape(text).gsub("\n", " ").gsub("\r", " ").
-
gsub(%r{"}, '"') # " WART
-
end
-
-
1
def _newline
-
637
return if @indent == 0
-
608
text! "\n"
-
end
-
-
1
def _indent
-
637
return if @indent == 0 || @level == 0
-
461
text!(" " * (@level * @indent))
-
end
-
-
1
def _nested_structures(block)
-
124
@level += 1
-
124
block.call(self)
-
ensure
-
124
@level -= 1
-
end
-
-
# If XmlBase.cache_method_calls = true, we dynamicly create the method
-
# missed as an instance method on the XMLBase object. Because XML
-
# documents are usually very repetative in nature, the next node will
-
# be handled by the new method instead of method_missing. As
-
# method_missing is very slow, this speeds up document generation
-
# significantly.
-
1
def cache_method_call(sym)
-
426
class << self; self; end.class_eval do
-
213
define_method(sym) do |*args, &block|
-
219
tag!(sym, *args, &block)
-
end
-
end
-
end
-
end
-
-
1
XmlBase.cache_method_calls = true
-
-
end
-
#!/usr/bin/env ruby
-
-
#--
-
# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
-
# All rights reserved.
-
-
# Permission is granted for use, copying, modification, distribution,
-
# and distribution of modified versions of this work as long as the
-
# above copyright notice is included.
-
#++
-
-
1
require 'builder/xmlmarkup'
-
-
1
module Builder
-
-
# Create a series of SAX-like XML events (e.g. start_tag, end_tag)
-
# from the markup code. XmlEvent objects are used in a way similar
-
# to XmlMarkup objects, except that a series of events are generated
-
# and passed to a handler rather than generating character-based
-
# markup.
-
#
-
# Usage:
-
# xe = Builder::XmlEvents.new(hander)
-
# xe.title("HI") # Sends start_tag/end_tag/text messages to the handler.
-
#
-
# Indentation may also be selected by providing value for the
-
# indentation size and initial indentation level.
-
#
-
# xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level)
-
#
-
# == XML Event Handler
-
#
-
# The handler object must expect the following events.
-
#
-
# [<tt>start_tag(tag, attrs)</tt>]
-
# Announces that a new tag has been found. +tag+ is the name of
-
# the tag and +attrs+ is a hash of attributes for the tag.
-
#
-
# [<tt>end_tag(tag)</tt>]
-
# Announces that an end tag for +tag+ has been found.
-
#
-
# [<tt>text(text)</tt>]
-
# Announces that a string of characters (+text+) has been found.
-
# A series of characters may be broken up into more than one
-
# +text+ call, so the client cannot assume that a single
-
# callback contains all the text data.
-
#
-
1
class XmlEvents < XmlMarkup
-
1
def text!(text)
-
@target.text(text)
-
end
-
-
1
def _start_tag(sym, attrs, end_too=false)
-
@target.start_tag(sym, attrs)
-
_end_tag(sym) if end_too
-
end
-
-
1
def _end_tag(sym)
-
@target.end_tag(sym)
-
end
-
end
-
-
end
-
#!/usr/bin/env ruby
-
#--
-
# Copyright 2004, 2005 by Jim Weirich (jim@weirichhouse.org).
-
# All rights reserved.
-
-
# Permission is granted for use, copying, modification, distribution,
-
# and distribution of modified versions of this work as long as the
-
# above copyright notice is included.
-
#++
-
-
# Provide a flexible and easy to use Builder for creating XML markup.
-
# See XmlBuilder for usage details.
-
-
1
require 'builder/xmlbase'
-
-
1
module Builder
-
-
# Create XML markup easily. All (well, almost all) methods sent to
-
# an XmlMarkup object will be translated to the equivalent XML
-
# markup. Any method with a block will be treated as an XML markup
-
# tag with nested markup in the block.
-
#
-
# Examples will demonstrate this easier than words. In the
-
# following, +xm+ is an +XmlMarkup+ object.
-
#
-
# xm.em("emphasized") # => <em>emphasized</em>
-
# xm.em { xm.b("emp & bold") } # => <em><b>emph & bold</b></em>
-
# xm.a("A Link", "href"=>"http://onestepback.org")
-
# # => <a href="http://onestepback.org">A Link</a>
-
# xm.div { xm.br } # => <div><br/></div>
-
# xm.target("name"=>"compile", "option"=>"fast")
-
# # => <target option="fast" name="compile"\>
-
# # NOTE: order of attributes is not specified.
-
#
-
# xm.instruct! # <?xml version="1.0" encoding="UTF-8"?>
-
# xm.html { # <html>
-
# xm.head { # <head>
-
# xm.title("History") # <title>History</title>
-
# } # </head>
-
# xm.body { # <body>
-
# xm.comment! "HI" # <!-- HI -->
-
# xm.h1("Header") # <h1>Header</h1>
-
# xm.p("paragraph") # <p>paragraph</p>
-
# } # </body>
-
# } # </html>
-
#
-
# == Notes:
-
#
-
# * The order that attributes are inserted in markup tags is
-
# undefined.
-
#
-
# * Sometimes you wish to insert text without enclosing tags. Use
-
# the <tt>text!</tt> method to accomplish this.
-
#
-
# Example:
-
#
-
# xm.div { # <div>
-
# xm.text! "line"; xm.br # line<br/>
-
# xm.text! "another line"; xmbr # another line<br/>
-
# } # </div>
-
#
-
# * The special XML characters <, >, and & are converted to <,
-
# > and & automatically. Use the <tt><<</tt> operation to
-
# insert text without modification.
-
#
-
# * Sometimes tags use special characters not allowed in ruby
-
# identifiers. Use the <tt>tag!</tt> method to handle these
-
# cases.
-
#
-
# Example:
-
#
-
# xml.tag!("SOAP:Envelope") { ... }
-
#
-
# will produce ...
-
#
-
# <SOAP:Envelope> ... </SOAP:Envelope>"
-
#
-
# <tt>tag!</tt> will also take text and attribute arguments (after
-
# the tag name) like normal markup methods. (But see the next
-
# bullet item for a better way to handle XML namespaces).
-
#
-
# * Direct support for XML namespaces is now available. If the
-
# first argument to a tag call is a symbol, it will be joined to
-
# the tag to produce a namespace:tag combination. It is easier to
-
# show this than describe it.
-
#
-
# xml.SOAP :Envelope do ... end
-
#
-
# Just put a space before the colon in a namespace to produce the
-
# right form for builder (e.g. "<tt>SOAP:Envelope</tt>" =>
-
# "<tt>xml.SOAP :Envelope</tt>")
-
#
-
# * XmlMarkup builds the markup in any object (called a _target_)
-
# that accepts the <tt><<</tt> method. If no target is given,
-
# then XmlMarkup defaults to a string target.
-
#
-
# Examples:
-
#
-
# xm = Builder::XmlMarkup.new
-
# result = xm.title("yada")
-
# # result is a string containing the markup.
-
#
-
# buffer = ""
-
# xm = Builder::XmlMarkup.new(buffer)
-
# # The markup is appended to buffer (using <<)
-
#
-
# xm = Builder::XmlMarkup.new(STDOUT)
-
# # The markup is written to STDOUT (using <<)
-
#
-
# xm = Builder::XmlMarkup.new
-
# x2 = Builder::XmlMarkup.new(:target=>xm)
-
# # Markup written to +x2+ will be send to +xm+.
-
#
-
# * Indentation is enabled by providing the number of spaces to
-
# indent for each level as a second argument to XmlBuilder.new.
-
# Initial indentation may be specified using a third parameter.
-
#
-
# Example:
-
#
-
# xm = Builder.new(:indent=>2)
-
# # xm will produce nicely formatted and indented XML.
-
#
-
# xm = Builder.new(:indent=>2, :margin=>4)
-
# # xm will produce nicely formatted and indented XML with 2
-
# # spaces per indent and an over all indentation level of 4.
-
#
-
# builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2)
-
# builder.name { |b| b.first("Jim"); b.last("Weirich) }
-
# # prints:
-
# # <name>
-
# # <first>Jim</first>
-
# # <last>Weirich</last>
-
# # </name>
-
#
-
# * The instance_eval implementation which forces self to refer to
-
# the message receiver as self is now obsolete. We now use normal
-
# block calls to execute the markup block. This means that all
-
# markup methods must now be explicitly send to the xml builder.
-
# For instance, instead of
-
#
-
# xml.div { strong("text") }
-
#
-
# you need to write:
-
#
-
# xml.div { xml.strong("text") }
-
#
-
# Although more verbose, the subtle change in semantics within the
-
# block was found to be prone to error. To make this change a
-
# little less cumbersome, the markup block now gets the markup
-
# object sent as an argument, allowing you to use a shorter alias
-
# within the block.
-
#
-
# For example:
-
#
-
# xml_builder = Builder::XmlMarkup.new
-
# xml_builder.div { |xml|
-
# xml.stong("text")
-
# }
-
#
-
1
class XmlMarkup < XmlBase
-
-
# Create an XML markup builder. Parameters are specified by an
-
# option hash.
-
#
-
# :target=><em>target_object</em>::
-
# Object receiving the markup. +target_object+ must respond to
-
# the <tt><<(<em>a_string</em>)</tt> operator and return
-
# itself. The default target is a plain string target.
-
#
-
# :indent=><em>indentation</em>::
-
# Number of spaces used for indentation. The default is no
-
# indentation and no line breaks.
-
#
-
# :margin=><em>initial_indentation_level</em>::
-
# Amount of initial indentation (specified in levels, not
-
# spaces).
-
#
-
# :escape_attrs=><em>OBSOLETE</em>::
-
# The :escape_attrs option is no longer supported by builder
-
# (and will be quietly ignored). String attribute values are
-
# now automatically escaped. If you need unescaped attribute
-
# values (perhaps you are using entities in the attribute
-
# values), then give the value as a Symbol. This allows much
-
# finer control over escaping attribute values.
-
#
-
1
def initialize(options={})
-
67
indent = options[:indent] || 0
-
67
margin = options[:margin] || 0
-
67
super(indent, margin)
-
67
@target = options[:target] || ""
-
end
-
-
# Return the target of the builder.
-
1
def target!
-
96
@target
-
end
-
-
1
def comment!(comment_text)
-
_ensure_no_block ::Kernel::block_given?
-
_special("<!-- ", " -->", comment_text, nil)
-
end
-
-
# Insert an XML declaration into the XML markup.
-
#
-
# For example:
-
#
-
# xml.declare! :ELEMENT, :blah, "yada"
-
# # => <!ELEMENT blah "yada">
-
1
def declare!(inst, *args, &block)
-
_indent
-
@target << "<!#{inst}"
-
args.each do |arg|
-
case arg
-
when ::String
-
@target << %{ "#{arg}"} # " WART
-
when ::Symbol
-
@target << " #{arg}"
-
end
-
end
-
if ::Kernel::block_given?
-
@target << " ["
-
_newline
-
_nested_structures(block)
-
@target << "]"
-
end
-
@target << ">"
-
_newline
-
end
-
-
# Insert a processing instruction into the XML markup. E.g.
-
#
-
# For example:
-
#
-
# xml.instruct!
-
# #=> <?xml version="1.0" encoding="UTF-8"?>
-
# xml.instruct! :aaa, :bbb=>"ccc"
-
# #=> <?aaa bbb="ccc"?>
-
#
-
# Note: If the encoding is setup to "UTF-8" and the value of
-
# $KCODE is "UTF8", then builder will emit UTF-8 encoded strings
-
# rather than the entity encoding normally used.
-
1
def instruct!(directive_tag=:xml, attrs={})
-
39
_ensure_no_block ::Kernel::block_given?
-
39
if directive_tag == :xml
-
36
a = { :version=>"1.0", :encoding=>"UTF-8" }
-
36
attrs = a.merge attrs
-
36
@encoding = attrs[:encoding].downcase
-
end
-
_special(
-
39
"<?#{directive_tag}",
-
"?>",
-
nil,
-
attrs,
-
[:version, :encoding, :standalone])
-
end
-
-
# Insert a CDATA section into the XML markup.
-
#
-
# For example:
-
#
-
# xml.cdata!("text to be included in cdata")
-
# #=> <![CDATA[text to be included in cdata]]>
-
#
-
1
def cdata!(text)
-
_ensure_no_block ::Kernel::block_given?
-
_special("<![CDATA[", "]]>", text.gsub(']]>', ']]]]><![CDATA[>'), nil)
-
end
-
-
1
private
-
-
# NOTE: All private methods of a builder object are prefixed when
-
# a "_" character to avoid possible conflict with XML tag names.
-
-
# Insert text directly in to the builder's target.
-
1
def _text(text)
-
1363
@target << text
-
end
-
-
# Insert special instruction.
-
1
def _special(open, close, data=nil, attrs=nil, order=[])
-
39
_indent
-
39
@target << open
-
39
@target << data if data
-
39
_insert_attributes(attrs, order) if attrs
-
39
@target << close
-
39
_newline
-
end
-
-
# Start an XML tag. If <tt>end_too</tt> is true, then the start
-
# tag is also the end tag (e.g. <br/>
-
1
def _start_tag(sym, attrs, end_too=false)
-
474
@target << "<#{sym}"
-
474
_insert_attributes(attrs)
-
474
@target << "/" if end_too
-
474
@target << ">"
-
end
-
-
# Insert an ending tag.
-
1
def _end_tag(sym)
-
409
@target << "</#{sym}>"
-
end
-
-
# Insert the attributes (given in the hash).
-
1
def _insert_attributes(attrs, order=[])
-
513
return if attrs.nil?
-
172
order.each do |k|
-
117
v = attrs[k]
-
117
@target << %{ #{k}="#{_attr_value(v)}"} if v # " WART
-
end
-
172
attrs.each do |k, v|
-
353
@target << %{ #{k}="#{_attr_value(v)}"} unless order.member?(k) # " WART
-
end
-
end
-
-
1
def _attr_value(value)
-
353
case value
-
when ::Symbol
-
value.to_s
-
else
-
353
_escape_attribute(value.to_s)
-
end
-
end
-
-
1
def _ensure_no_block(got_block)
-
39
if got_block
-
::Kernel::raise IllegalBlockError.new(
-
"Blocks are not allowed on XML instructions"
-
)
-
end
-
end
-
-
end
-
-
end
-
1
require 'dalli/client'
-
1
require 'dalli/ring'
-
1
require 'dalli/server'
-
1
require 'dalli/socket'
-
1
require 'dalli/version'
-
1
require 'dalli/options'
-
1
require 'dalli/compressor'
-
1
require 'dalli/railtie' if defined?(::Rails::Railtie)
-
-
1
module Dalli
-
# generic error
-
1
class DalliError < RuntimeError; end
-
# socket/server communication error
-
1
class NetworkError < DalliError; end
-
# no server available/alive error
-
1
class RingError < DalliError; end
-
# application error in marshalling serialization
-
1
class MarshalError < DalliError; end
-
# application error in marshalling deserialization or decompression
-
1
class UnmarshalError < DalliError; end
-
-
1
def self.logger
-
8
@logger ||= (rails_logger || default_logger)
-
end
-
-
1
def self.rails_logger
-
1
(defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
-
2
(defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER)
-
end
-
-
1
def self.default_logger
-
1
require 'logger'
-
1
l = Logger.new(STDOUT)
-
1
l.level = Logger::INFO
-
1
l
-
end
-
-
1
def self.logger=(logger)
-
@logger = logger
-
end
-
-
# Default serialization to Marshal
-
1
@serializer = Marshal
-
-
1
def self.serializer
-
28
@serializer
-
end
-
-
1
def self.serializer=(serializer)
-
@serializer = serializer
-
end
-
-
# Default serialization to Dalli::Compressor
-
1
@compressor = Compressor
-
-
1
def self.compressor
-
@compressor
-
end
-
-
1
def self.compressor=(compressor)
-
@compressor = compressor
-
end
-
end
-
-
1
if defined?(RAILS_VERSION) && RAILS_VERSION < '3'
-
raise Dalli::DalliError, "Dalli #{Dalli::VERSION} does not support Rails version < 3.0"
-
end
-
1
require 'digest/md5'
-
1
require 'set'
-
-
# encoding: ascii
-
1
module Dalli
-
1
class Client
-
-
##
-
# Dalli::Client is the main class which developers will use to interact with
-
# the memcached server. Usage:
-
#
-
# Dalli::Client.new(['localhost:11211:10', 'cache-2.example.com:11211:5', '192.168.0.1:22122:5'],
-
# :threadsafe => true, :failover => true, :expires_in => 300)
-
#
-
# servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
-
# Both weight and port are optional. If you pass in nil, Dalli will use the <tt>MEMCACHE_SERVERS</tt>
-
# environment variable or default to 'localhost:11211' if it is not present.
-
#
-
# Options:
-
# - :namespace - prepend each key with this value to provide simple namespacing.
-
# - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
-
# - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
-
# - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults to 0 or forever
-
# - :compress - defaults to false, if true Dalli will compress values larger than 1024 bytes before
-
# sending them to memcached.
-
#
-
1
def initialize(servers=nil, options={})
-
10
@servers = servers || env_servers || '127.0.0.1:11211'
-
10
@options = normalize_options(options)
-
10
@ring = nil
-
10
@servers_in_use = nil
-
end
-
-
#
-
# The standard memcached instruction set
-
#
-
-
##
-
# Turn on quiet aka noreply support.
-
# All relevant operations within this block will be effectively
-
# pipelined as Dalli will use 'quiet' operations where possible.
-
# Currently supports the set, add, replace and delete operations.
-
1
def multi
-
old, Thread.current[:dalli_multi] = Thread.current[:dalli_multi], true
-
yield
-
ensure
-
Thread.current[:dalli_multi] = old
-
end
-
-
1
def get(key, options=nil)
-
8
resp = perform(:get, key)
-
8
resp.nil? || resp == 'Not found' ? nil : resp
-
end
-
-
##
-
# Fetch multiple keys efficiently.
-
# Returns a hash of { 'key' => 'value', 'key2' => 'value1' }
-
1
def get_multi(*keys)
-
return {} if keys.empty?
-
options = nil
-
options = keys.pop if keys.last.is_a?(Hash) || keys.last.nil?
-
ring.lock do
-
self.servers_in_use = Set.new
-
-
keys.flatten.each do |key|
-
begin
-
perform(:getkq, key)
-
rescue DalliError, NetworkError => e
-
Dalli.logger.debug { e.inspect }
-
Dalli.logger.debug { "unable to get key #{key}" }
-
end
-
end
-
-
values = {}
-
servers_in_use.each do |server|
-
next unless server.alive?
-
begin
-
server.request(:noop).each_pair do |key, value|
-
values[key_without_namespace(key)] = value
-
end
-
rescue DalliError, NetworkError => e
-
Dalli.logger.debug { e.inspect }
-
Dalli.logger.debug { "results from this server will be missing" }
-
end
-
end
-
values
-
end
-
ensure
-
self.servers_in_use = nil
-
end
-
-
1
def fetch(key, ttl=nil, options=nil)
-
ttl ||= @options[:expires_in].to_i
-
val = get(key, options)
-
if val.nil? && block_given?
-
val = yield
-
add(key, val, ttl, options)
-
end
-
val
-
end
-
-
##
-
# compare and swap values using optimistic locking.
-
# Fetch the existing value for key.
-
# If it exists, yield the value to the block.
-
# Add the block's return value as the new value for the key.
-
# Add will fail if someone else changed the value.
-
#
-
# Returns:
-
# - nil if the key did not exist.
-
# - false if the value was changed by someone else.
-
# - true if the value was successfully updated.
-
1
def cas(key, ttl=nil, options=nil, &block)
-
ttl ||= @options[:expires_in].to_i
-
(value, cas) = perform(:cas, key)
-
value = (!value || value == 'Not found') ? nil : value
-
if value
-
newvalue = block.call(value)
-
perform(:set, key, newvalue, ttl, cas, options)
-
end
-
end
-
-
1
def set(key, value, ttl=nil, options=nil)
-
13
ttl ||= @options[:expires_in].to_i
-
13
perform(:set, key, value, ttl, 0, options)
-
end
-
-
##
-
# Conditionally add a key/value pair, if the key does not already exist
-
# on the server. Returns true if the operation succeeded.
-
1
def add(key, value, ttl=nil, options=nil)
-
10
ttl ||= @options[:expires_in].to_i
-
10
perform(:add, key, value, ttl, options)
-
end
-
-
##
-
# Conditionally add a key/value pair, only if the key already exists
-
# on the server. Returns true if the operation succeeded.
-
1
def replace(key, value, ttl=nil, options=nil)
-
ttl ||= @options[:expires_in].to_i
-
perform(:replace, key, value, ttl, options)
-
end
-
-
1
def delete(key)
-
2
perform(:delete, key)
-
end
-
-
##
-
# Append value to the value already stored on the server for 'key'.
-
# Appending only works for values stored with :raw => true.
-
1
def append(key, value)
-
perform(:append, key, value.to_s)
-
end
-
-
##
-
# Prepend value to the value already stored on the server for 'key'.
-
# Prepending only works for values stored with :raw => true.
-
1
def prepend(key, value)
-
perform(:prepend, key, value.to_s)
-
end
-
-
1
def flush(delay=0)
-
time = -delay
-
ring.servers.map { |s| s.request(:flush, time += delay) }
-
end
-
-
1
alias_method :flush_all, :flush
-
-
##
-
# Incr adds the given amount to the counter on the memcached server.
-
# Amt must be a positive integer value.
-
#
-
# If default is nil, the counter must already exist or the operation
-
# will fail and will return nil. Otherwise this method will return
-
# the new value for the counter.
-
#
-
# Note that the ttl will only apply if the counter does not already
-
# exist. To increase an existing counter and update its TTL, use
-
# #cas.
-
1
def incr(key, amt=1, ttl=nil, default=nil)
-
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
-
ttl ||= @options[:expires_in].to_i
-
perform(:incr, key, amt.to_i, ttl, default)
-
end
-
-
##
-
# Decr subtracts the given amount from the counter on the memcached server.
-
# Amt must be a positive integer value.
-
#
-
# memcached counters are unsigned and cannot hold negative values. Calling
-
# decr on a counter which is 0 will just return 0.
-
#
-
# If default is nil, the counter must already exist or the operation
-
# will fail and will return nil. Otherwise this method will return
-
# the new value for the counter.
-
#
-
# Note that the ttl will only apply if the counter does not already
-
# exist. To decrease an existing counter and update its TTL, use
-
# #cas.
-
1
def decr(key, amt=1, ttl=nil, default=nil)
-
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
-
ttl ||= @options[:expires_in].to_i
-
perform(:decr, key, amt.to_i, ttl, default)
-
end
-
-
##
-
# Touch updates expiration time for a given key.
-
#
-
# Returns true if key exists, otherwise nil.
-
1
def touch(key, ttl=nil)
-
ttl ||= @options[:expires_in].to_i
-
resp = perform(:touch, key, ttl)
-
resp.nil? ? nil : true
-
end
-
-
##
-
# Collect the stats for each server.
-
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
-
1
def stats
-
1
values = {}
-
1
ring.servers.each do |server|
-
1
values["#{server.hostname}:#{server.port}"] = server.alive? ? server.request(:stats) : nil
-
end
-
1
values
-
end
-
-
##
-
# Reset stats for each server.
-
1
def reset_stats
-
ring.servers.map do |server|
-
server.alive? ? server.request(:reset_stats) : nil
-
end
-
end
-
-
##
-
# Close our connection to each server.
-
# If you perform another operation after this, the connections will be re-established.
-
1
def close
-
if @ring
-
@ring.servers.each { |s| s.close }
-
@ring = nil
-
end
-
end
-
1
alias_method :reset, :close
-
-
1
private
-
-
1
def ring
-
@ring ||= Dalli::Ring.new(
-
Array(@servers).map do |s|
-
8
server_options = {}
-
8
if s =~ %r{\Amemcached://}
-
uri = URI.parse(s)
-
server_options[:username] = uri.user
-
server_options[:password] = uri.password
-
s = "#{uri.host}:#{uri.port}"
-
end
-
8
Dalli::Server.new(s, @options.merge(server_options))
-
end, @options
-
34
)
-
end
-
-
1
def env_servers
-
ENV['MEMCACHE_SERVERS'] ? ENV['MEMCACHE_SERVERS'].split(',') : nil
-
end
-
-
# Chokepoint method for instrumentation
-
1
def perform(op, key, *args)
-
33
key = key.to_s
-
33
key = validate_key(key)
-
33
begin
-
33
server = ring.server_for_key(key)
-
33
ret = server.request(op, key, *args)
-
33
servers_in_use << server if servers_in_use
-
33
ret
-
rescue NetworkError => e
-
Dalli.logger.debug { e.inspect }
-
Dalli.logger.debug { "retrying request with new server" }
-
retry
-
end
-
end
-
-
1
def servers_in_use
-
33
Thread.current[:"#{object_id}-servers"]
-
end
-
-
1
def servers_in_use=(value)
-
Thread.current[:"#{object_id}-servers"] = value
-
end
-
-
1
def validate_key(key)
-
33
raise ArgumentError, "key cannot be blank" if !key || key.length == 0
-
33
key = key_with_namespace(key)
-
33
if key.length > 250
-
namespace_length = @options[:namespace] ? @options[:namespace].size : 0
-
max_length_before_namespace = 212 - namespace_length
-
key = "#{key[0, max_length_before_namespace]}:md5:#{Digest::MD5.hexdigest(key)}"
-
end
-
33
return key
-
end
-
-
1
def key_with_namespace(key)
-
33
@options[:namespace] ? "#{@options[:namespace]}:#{key}" : key
-
end
-
-
1
def key_without_namespace(key)
-
@options[:namespace] ? key.sub(%r(\A#{@options[:namespace]}:), '') : key
-
end
-
-
1
def normalize_options(opts)
-
10
if opts[:compression]
-
Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true. Please update your configuration."
-
opts[:compress] = opts.delete(:compression)
-
end
-
10
begin
-
10
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
-
rescue NoMethodError
-
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
-
end
-
10
opts
-
end
-
end
-
end
-
1
require 'zlib'
-
-
1
module Dalli
-
1
class Compressor
-
1
def self.compress(data)
-
Zlib::Deflate.deflate(data)
-
end
-
-
1
def self.decompress(data)
-
Zlib::Inflate.inflate(data)
-
end
-
end
-
end
-
1
require 'thread'
-
1
require 'monitor'
-
-
1
module Dalli
-
-
# Make Dalli threadsafe by using a lock around all
-
# public server methods.
-
#
-
# Dalli::Server.extend(Dalli::Threadsafe)
-
#
-
1
module Threadsafe
-
1
def self.extended(obj)
-
8
obj.init_threadsafe
-
end
-
-
1
def request(op, *args)
-
34
@lock.synchronize do
-
34
super
-
end
-
end
-
-
1
def alive?
-
68
@lock.synchronize do
-
68
super
-
end
-
end
-
-
1
def close
-
@lock.synchronize do
-
super
-
end
-
end
-
-
1
def lock!
-
@lock.mon_enter
-
end
-
-
1
def unlock!
-
@lock.mon_exit
-
end
-
-
1
def init_threadsafe
-
8
@lock = Monitor.new
-
end
-
end
-
end
-
1
module Dalli
-
1
class Railtie < ::Rails::Railtie
-
1
config.before_configuration do
-
config.cache_store = :dalli_store
-
end
-
end
-
end
-
1
require 'digest/sha1'
-
1
require 'zlib'
-
-
1
module Dalli
-
1
class Ring
-
1
POINTS_PER_SERVER = 160 # this is the default in libmemcached
-
-
1
attr_accessor :servers, :continuum
-
-
1
def initialize(servers, options)
-
8
@servers = servers
-
8
@continuum = nil
-
8
if servers.size > 1
-
total_weight = servers.inject(0) { |memo, srv| memo + srv.weight }
-
continuum = []
-
servers.each do |server|
-
entry_count_for(server, servers.size, total_weight).times do |idx|
-
hash = Digest::SHA1.hexdigest("#{server.hostname}:#{server.port}:#{idx}")
-
value = Integer("0x#{hash[0..7]}")
-
continuum << Dalli::Ring::Entry.new(value, server)
-
end
-
end
-
@continuum = continuum.sort { |a, b| a.value <=> b.value }
-
end
-
-
8
threadsafe! unless options[:threadsafe] == false
-
8
@failover = options[:failover] != false
-
end
-
-
1
def server_for_key(key)
-
33
if @continuum
-
hkey = hash_for(key)
-
20.times do |try|
-
entryidx = self.class.binary_search(@continuum, hkey)
-
server = @continuum[entryidx].server
-
return server if server.alive?
-
break unless @failover
-
hkey = hash_for("#{try}#{key}")
-
end
-
else
-
33
server = @servers.first
-
33
return server if server && server.alive?
-
end
-
-
raise Dalli::RingError, "No server available"
-
end
-
-
1
def lock
-
@servers.each { |s| s.lock! }
-
begin
-
return yield
-
ensure
-
@servers.each { |s| s.unlock! }
-
end
-
end
-
-
1
private
-
-
1
def threadsafe!
-
8
@servers.each do |s|
-
8
s.extend(Dalli::Threadsafe)
-
end
-
end
-
-
1
def hash_for(key)
-
Zlib.crc32(key)
-
end
-
-
1
def entry_count_for(server, total_servers, total_weight)
-
((total_servers * POINTS_PER_SERVER * server.weight) / Float(total_weight)).floor
-
end
-
-
# Find the closest index in the Ring with value <= the given value
-
1
def self.binary_search(ary, value)
-
upper = ary.size - 1
-
lower = 0
-
idx = 0
-
-
while (lower <= upper) do
-
idx = (lower + upper) / 2
-
comp = ary[idx].value <=> value
-
-
if comp == 0
-
return idx
-
elsif comp > 0
-
upper = idx - 1
-
else
-
lower = idx + 1
-
end
-
end
-
return upper
-
end
-
-
1
class Entry
-
1
attr_reader :value
-
1
attr_reader :server
-
-
1
def initialize(val, srv)
-
@value = val
-
@server = srv
-
end
-
end
-
-
end
-
end
-
1
require 'socket'
-
1
require 'timeout'
-
-
1
module Dalli
-
1
class Server
-
1
attr_accessor :hostname
-
1
attr_accessor :port
-
1
attr_accessor :weight
-
1
attr_accessor :options
-
-
1
DEFAULTS = {
-
# seconds between trying to contact a remote server
-
:down_retry_delay => 1,
-
# connect/read/write timeout for socket operations
-
:socket_timeout => 0.5,
-
# times a socket operation may fail before considering the server dead
-
:socket_max_failures => 2,
-
# amount of time to sleep between retries when a failure occurs
-
:socket_failure_delay => 0.01,
-
# max size of value in bytes (default is 1 MB, can be overriden with "memcached -I <size>")
-
:value_max_bytes => 1024 * 1024,
-
# min byte size to attempt compression
-
:compression_min_size => 1024,
-
# max byte size for compression
-
:compression_max_size => false,
-
:username => nil,
-
:password => nil,
-
:keepalive => true
-
}
-
-
1
def initialize(attribs, options = {})
-
8
(@hostname, @port, @weight) = attribs.split(':')
-
8
@port ||= 11211
-
8
@port = Integer(@port)
-
8
@weight ||= 1
-
8
@weight = Integer(@weight)
-
8
@fail_count = 0
-
8
@down_at = nil
-
8
@last_down_at = nil
-
8
@options = DEFAULTS.merge(options)
-
8
@sock = nil
-
8
@msg = nil
-
8
@pid = nil
-
8
@inprogress = nil
-
end
-
-
# Chokepoint method for instrumentation
-
1
def request(op, *args)
-
34
verify_state
-
34
raise Dalli::NetworkError, "#{hostname}:#{port} is down: #{@error} #{@msg}" unless alive?
-
34
begin
-
34
send(op, *args)
-
rescue Dalli::NetworkError
-
raise
-
rescue Dalli::MarshalError => ex
-
Dalli.logger.error "Marshalling error for key '#{args.first}': #{ex.message}"
-
Dalli.logger.error "You are trying to cache a Ruby object which cannot be serialized to memcached."
-
Dalli.logger.error ex.backtrace.join("\n\t")
-
false
-
rescue Dalli::DalliError
-
raise
-
rescue => ex
-
Dalli.logger.error "Unexpected exception in Dalli: #{ex.class.name}: #{ex.message}"
-
Dalli.logger.error "This is a bug in Dalli, please enter an issue in Github if it does not already exist."
-
Dalli.logger.error ex.backtrace.join("\n\t")
-
down!
-
end
-
end
-
-
1
def alive?
-
68
return true if @sock
-
-
8
if @last_down_at && @last_down_at + options[:down_retry_delay] >= Time.now
-
time = @last_down_at + options[:down_retry_delay] - Time.now
-
Dalli.logger.debug { "down_retry_delay not reached for #{hostname}:#{port} (%.3f seconds left)" % time }
-
return false
-
end
-
-
8
connect
-
8
!!@sock
-
rescue Dalli::NetworkError
-
false
-
end
-
-
1
def close
-
return unless @sock
-
@sock.close rescue nil
-
@sock = nil
-
@pid = nil
-
@inprogress = false
-
end
-
-
1
def lock!
-
end
-
-
1
def unlock!
-
end
-
-
# NOTE: Additional public methods should be overridden in Dalli::Threadsafe
-
-
1
private
-
-
1
def verify_state
-
34
failure! if @inprogress
-
34
failure! if @pid && @pid != Process.pid
-
end
-
-
1
def failure!
-
Dalli.logger.info { "#{hostname}:#{port} failed (count: #{@fail_count})" }
-
-
@fail_count += 1
-
if @fail_count >= options[:socket_max_failures]
-
down!
-
else
-
close
-
sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
-
raise Dalli::NetworkError, "Socket operation failed, retrying..."
-
end
-
end
-
-
1
def down!
-
close
-
-
@last_down_at = Time.now
-
-
if @down_at
-
time = Time.now - @down_at
-
Dalli.logger.debug { "#{hostname}:#{port} is still down (for %.3f seconds now)" % time }
-
else
-
@down_at = @last_down_at
-
Dalli.logger.warn { "#{hostname}:#{port} is down" }
-
end
-
-
@error = $! && $!.class.name
-
@msg = @msg || ($! && $!.message && !$!.message.empty? && $!.message)
-
raise Dalli::NetworkError, "#{hostname}:#{port} is down: #{@error} #{@msg}"
-
end
-
-
1
def up!
-
8
if @down_at
-
time = Time.now - @down_at
-
Dalli.logger.warn { "#{hostname}:#{port} is back (downtime was %.3f seconds)" % time }
-
end
-
-
8
@fail_count = 0
-
8
@down_at = nil
-
8
@last_down_at = nil
-
8
@msg = nil
-
8
@error = nil
-
end
-
-
1
def multi?
-
50
Thread.current[:dalli_multi]
-
end
-
-
1
def get(key)
-
8
req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
-
8
write(req)
-
8
generic_response(true)
-
end
-
-
1
def getkq(key)
-
req = [REQUEST, OPCODES[:getkq], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:getkq])
-
write(req)
-
end
-
-
1
def set(key, value, ttl, cas, options)
-
13
(value, flags) = serialize(key, value, options)
-
-
13
if under_max_value_size?(value)
-
13
req = [REQUEST, OPCODES[multi? ? :setq : :set], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, cas, flags, ttl, key, value].pack(FORMAT[:set])
-
13
write(req)
-
13
generic_response unless multi?
-
else
-
false
-
end
-
end
-
-
1
def add(key, value, ttl, options)
-
10
(value, flags) = serialize(key, value, options)
-
-
10
if under_max_value_size?(value)
-
10
req = [REQUEST, OPCODES[multi? ? :addq : :add], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, 0, flags, ttl, key, value].pack(FORMAT[:add])
-
10
write(req)
-
10
generic_response unless multi?
-
else
-
false
-
end
-
end
-
-
1
def replace(key, value, ttl, options)
-
(value, flags) = serialize(key, value, options)
-
-
if under_max_value_size?(value)
-
req = [REQUEST, OPCODES[multi? ? :replaceq : :replace], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, 0, flags, ttl, key, value].pack(FORMAT[:replace])
-
write(req)
-
generic_response unless multi?
-
else
-
false
-
end
-
end
-
-
1
def delete(key)
-
2
req = [REQUEST, OPCODES[multi? ? :deleteq : :delete], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:delete])
-
2
write(req)
-
2
generic_response unless multi?
-
end
-
-
1
def flush(ttl)
-
req = [REQUEST, OPCODES[:flush], 0, 4, 0, 0, 4, 0, 0, 0].pack(FORMAT[:flush])
-
write(req)
-
generic_response
-
end
-
-
1
def decr(key, count, ttl, default)
-
expiry = default ? ttl : 0xFFFFFFFF
-
default ||= 0
-
(h, l) = split(count)
-
(dh, dl) = split(default)
-
req = [REQUEST, OPCODES[:decr], key.bytesize, 20, 0, 0, key.bytesize + 20, 0, 0, h, l, dh, dl, expiry, key].pack(FORMAT[:decr])
-
write(req)
-
body = generic_response
-
body ? longlong(*body.unpack('NN')) : body
-
end
-
-
1
def incr(key, count, ttl, default)
-
expiry = default ? ttl : 0xFFFFFFFF
-
default ||= 0
-
(h, l) = split(count)
-
(dh, dl) = split(default)
-
req = [REQUEST, OPCODES[:incr], key.bytesize, 20, 0, 0, key.bytesize + 20, 0, 0, h, l, dh, dl, expiry, key].pack(FORMAT[:incr])
-
write(req)
-
body = generic_response
-
body ? longlong(*body.unpack('NN')) : body
-
end
-
-
# Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
-
# We need to read all the responses at once.
-
1
def noop
-
req = [REQUEST, OPCODES[:noop], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
-
write(req)
-
multi_response
-
end
-
-
1
def append(key, value)
-
req = [REQUEST, OPCODES[:append], key.bytesize, 0, 0, 0, value.bytesize + key.bytesize, 0, 0, key, value].pack(FORMAT[:append])
-
write(req)
-
generic_response
-
end
-
-
1
def prepend(key, value)
-
req = [REQUEST, OPCODES[:prepend], key.bytesize, 0, 0, 0, value.bytesize + key.bytesize, 0, 0, key, value].pack(FORMAT[:prepend])
-
write(req)
-
generic_response
-
end
-
-
1
def stats(info='')
-
1
req = [REQUEST, OPCODES[:stat], info.bytesize, 0, 0, 0, info.bytesize, 0, 0, info].pack(FORMAT[:stat])
-
1
write(req)
-
1
keyvalue_response
-
end
-
-
1
def reset_stats
-
req = [REQUEST, OPCODES[:stat], 'reset'.bytesize, 0, 0, 0, 'reset'.bytesize, 0, 0, 'reset'].pack(FORMAT[:stat])
-
write(req)
-
generic_response
-
end
-
-
1
def cas(key)
-
req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
-
write(req)
-
cas_response
-
end
-
-
1
def version
-
8
req = [REQUEST, OPCODES[:version], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
-
8
write(req)
-
8
generic_response
-
end
-
-
1
def touch(key, ttl)
-
req = [REQUEST, OPCODES[:touch], key.bytesize, 4, 0, 0, key.bytesize + 4, 0, 0, ttl, key].pack(FORMAT[:touch])
-
write(req)
-
generic_response
-
end
-
-
# http://www.hjp.at/zettel/m/memcached_flags.rxml
-
# Looks like most clients use bit 0 to indicate native language serialization
-
# and bit 1 to indicate gzip compression.
-
1
FLAG_SERIALIZED = 0x1
-
1
FLAG_COMPRESSED = 0x2
-
-
1
def serialize(key, value, options=nil)
-
23
marshalled = false
-
23
value = unless options && options[:raw]
-
23
marshalled = true
-
23
begin
-
23
Dalli.serializer.dump(value)
-
rescue => ex
-
# Marshalling can throw several different types of generic Ruby exceptions.
-
# Convert to a specific exception so we can special case it higher up the stack.
-
exc = Dalli::MarshalError.new(ex.message)
-
exc.set_backtrace ex.backtrace
-
raise exc
-
end
-
else
-
value.to_s
-
end
-
23
compressed = false
-
23
if @options[:compress] && value.bytesize >= @options[:compression_min_size] &&
-
(!@options[:compression_max_size] || value.bytesize <= @options[:compression_max_size])
-
value = Dalli.compressor.compress(value)
-
compressed = true
-
end
-
-
23
flags = 0
-
23
flags |= FLAG_COMPRESSED if compressed
-
23
flags |= FLAG_SERIALIZED if marshalled
-
23
[value, flags]
-
end
-
-
1
def deserialize(value, flags)
-
5
value = Dalli.compressor.decompress(value) if (flags & FLAG_COMPRESSED) != 0
-
5
value = Dalli.serializer.load(value) if (flags & FLAG_SERIALIZED) != 0
-
5
value
-
rescue TypeError
-
raise if $!.message !~ /needs to have method `_load'|exception class\/object expected|instance of IO needed|incompatible marshal file format/
-
raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
-
rescue ArgumentError
-
raise if $!.message !~ /undefined class|marshal data too short/
-
raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
-
rescue Zlib::Error
-
raise UnmarshalError, "Unable to uncompress value: #{$!.message}"
-
end
-
-
1
def cas_response
-
header = read(24)
-
raise Dalli::NetworkError, 'No response' if !header
-
(extras, _, status, count, _, cas) = header.unpack(CAS_HEADER)
-
data = read(count) if count > 0
-
if status == 1
-
nil
-
elsif status != 0
-
raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
-
elsif data
-
flags = data[0...extras].unpack('N')[0]
-
value = data[extras..-1]
-
data = deserialize(value, flags)
-
end
-
[data, cas]
-
end
-
-
1
CAS_HEADER = '@4CCnNNQ'
-
1
NORMAL_HEADER = '@4CCnN'
-
1
KV_HEADER = '@2n@6nN'
-
-
1
def under_max_value_size?(value)
-
23
value.bytesize <= @options[:value_max_bytes]
-
end
-
-
1
def generic_response(unpack=false)
-
41
header = read(24)
-
41
raise Dalli::NetworkError, 'No response' if !header
-
41
(extras, _, status, count) = header.unpack(NORMAL_HEADER)
-
41
data = read(count) if count > 0
-
41
if status == 1
-
nil
-
38
elsif status == 2 || status == 5
-
false # Not stored, normal status for add operation
-
38
elsif status != 0
-
raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
-
38
elsif data
-
13
flags = data[0...extras].unpack('N')[0]
-
13
value = data[extras..-1]
-
13
unpack ? deserialize(value, flags) : value
-
else
-
25
true
-
end
-
end
-
-
1
def keyvalue_response
-
1
hash = {}
-
1
loop do
-
49
header = read(24)
-
49
raise Dalli::NetworkError, 'No response' if !header
-
49
(key_length, _, body_length) = header.unpack(KV_HEADER)
-
49
return hash if key_length == 0
-
48
key = read(key_length)
-
48
value = read(body_length - key_length) if body_length - key_length > 0
-
48
hash[key] = value
-
end
-
end
-
-
1
def multi_response
-
hash = {}
-
loop do
-
header = read(24)
-
raise Dalli::NetworkError, 'No response' if !header
-
(key_length, _, body_length) = header.unpack(KV_HEADER)
-
return hash if key_length == 0
-
flags = read(4).unpack('N')[0]
-
key = read(key_length)
-
value = read(body_length - key_length - 4) if body_length - key_length - 4 > 0
-
hash[key] = deserialize(value, flags)
-
end
-
end
-
-
1
def write(bytes)
-
42
begin
-
42
@inprogress = true
-
42
result = @sock.write(bytes)
-
42
@inprogress = false
-
42
result
-
rescue SystemCallError, Timeout::Error
-
failure!
-
end
-
end
-
-
1
def read(count)
-
202
begin
-
202
@inprogress = true
-
202
data = @sock.readfull(count)
-
202
@inprogress = false
-
202
data
-
rescue SystemCallError, Timeout::Error, EOFError
-
failure!
-
end
-
end
-
-
1
def connect
-
8
Dalli.logger.debug { "Dalli::Server#connect #{hostname}:#{port}" }
-
-
8
begin
-
8
@pid = Process.pid
-
8
@sock = KSocket.open(hostname, port, options)
-
8
@version = version # trigger actual connect
-
8
sasl_authentication if need_auth?
-
8
up!
-
rescue Dalli::DalliError # SASL auth failure
-
raise
-
rescue SystemCallError, Timeout::Error, EOFError, SocketError
-
# SocketError = DNS resolution failure
-
failure!
-
end
-
end
-
-
1
def split(n)
-
[n >> 32, 0xFFFFFFFF & n]
-
end
-
-
1
def longlong(a, b)
-
(a << 32) | b
-
end
-
-
1
REQUEST = 0x80
-
1
RESPONSE = 0x81
-
-
1
RESPONSE_CODES = {
-
0 => 'No error',
-
1 => 'Key not found',
-
2 => 'Key exists',
-
3 => 'Value too large',
-
4 => 'Invalid arguments',
-
5 => 'Item not stored',
-
6 => 'Incr/decr on a non-numeric value',
-
0x20 => 'Authentication required',
-
0x81 => 'Unknown command',
-
0x82 => 'Out of memory',
-
}
-
-
1
OPCODES = {
-
:get => 0x00,
-
:set => 0x01,
-
:add => 0x02,
-
:replace => 0x03,
-
:delete => 0x04,
-
:incr => 0x05,
-
:decr => 0x06,
-
:flush => 0x08,
-
:noop => 0x0A,
-
:version => 0x0B,
-
:getkq => 0x0D,
-
:append => 0x0E,
-
:prepend => 0x0F,
-
:stat => 0x10,
-
:setq => 0x11,
-
:addq => 0x12,
-
:replaceq => 0x13,
-
:deleteq => 0x14,
-
:incrq => 0x15,
-
:decrq => 0x16,
-
:auth_negotiation => 0x20,
-
:auth_request => 0x21,
-
:auth_continue => 0x22,
-
:touch => 0x1C,
-
}
-
-
1
HEADER = "CCnCCnNNQ"
-
1
OP_FORMAT = {
-
:get => 'a*',
-
:set => 'NNa*a*',
-
:add => 'NNa*a*',
-
:replace => 'NNa*a*',
-
:delete => 'a*',
-
:incr => 'NNNNNa*',
-
:decr => 'NNNNNa*',
-
:flush => 'N',
-
:noop => '',
-
:getkq => 'a*',
-
:version => '',
-
:stat => 'a*',
-
:append => 'a*a*',
-
:prepend => 'a*a*',
-
:auth_request => 'a*a*',
-
:auth_continue => 'a*a*',
-
:touch => 'Na*',
-
}
-
18
FORMAT = OP_FORMAT.inject({}) { |memo, (k, v)| memo[k] = HEADER + v; memo }
-
-
-
#######
-
# SASL authentication support for NorthScale
-
#######
-
-
1
def need_auth?
-
8
@options[:username] || ENV['MEMCACHE_USERNAME']
-
end
-
-
1
def username
-
@options[:username] || ENV['MEMCACHE_USERNAME']
-
end
-
-
1
def password
-
@options[:password] || ENV['MEMCACHE_PASSWORD']
-
end
-
-
1
def sasl_authentication
-
Dalli.logger.info { "Dalli/SASL authenticating as #{username}" }
-
-
# negotiate
-
req = [REQUEST, OPCODES[:auth_negotiation], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
-
write(req)
-
header = read(24)
-
raise Dalli::NetworkError, 'No response' if !header
-
(extras, type, status, count) = header.unpack(NORMAL_HEADER)
-
raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
-
content = read(count)
-
return (Dalli.logger.debug("Authentication not required/supported by server")) if status == 0x81
-
mechanisms = content.split(' ')
-
raise NotImplementedError, "Dalli only supports the PLAIN authentication mechanism" if !mechanisms.include?('PLAIN')
-
-
# request
-
mechanism = 'PLAIN'
-
msg = "\x0#{username}\x0#{password}"
-
req = [REQUEST, OPCODES[:auth_request], mechanism.bytesize, 0, 0, 0, mechanism.bytesize + msg.bytesize, 0, 0, mechanism, msg].pack(FORMAT[:auth_request])
-
write(req)
-
-
header = read(24)
-
raise Dalli::NetworkError, 'No response' if !header
-
(extras, type, status, count) = header.unpack(NORMAL_HEADER)
-
raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
-
content = read(count)
-
return Dalli.logger.info("Dalli/SASL: #{content}") if status == 0
-
-
raise Dalli::DalliError, "Error authenticating: #{status}" unless status == 0x21
-
raise NotImplementedError, "No two-step authentication mechanisms supported"
-
# (step, msg) = sasl.receive('challenge', content)
-
# raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
-
end
-
end
-
end
-
1
begin
-
1
require 'kgio'
-
puts "Using kgio socket IO" if defined?($TESTING) && $TESTING
-
-
class Dalli::Server::KSocket < Kgio::Socket
-
attr_accessor :options
-
-
def kgio_wait_readable
-
IO.select([self], nil, nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
-
end
-
-
def kgio_wait_writable
-
IO.select(nil, [self], nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
-
end
-
-
def self.open(host, port, options = {})
-
addr = Socket.pack_sockaddr_in(port, host)
-
sock = start(addr)
-
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
-
sock.options = options
-
sock.kgio_wait_writable
-
sock
-
end
-
-
alias :write :kgio_write
-
-
def readfull(count)
-
value = ''
-
loop do
-
value << kgio_read!(count - value.bytesize)
-
break if value.bytesize == count
-
end
-
value
-
end
-
-
end
-
-
if ::Kgio.respond_to?(:wait_readable=)
-
::Kgio.wait_readable = :kgio_wait_readable
-
::Kgio.wait_writable = :kgio_wait_writable
-
end
-
-
rescue LoadError
-
-
1
puts "Using standard socket IO (#{RUBY_DESCRIPTION})" if defined?($TESTING) && $TESTING
-
1
class Dalli::Server::KSocket < TCPSocket
-
1
attr_accessor :options
-
-
1
def self.open(host, port, options = {})
-
8
Timeout.timeout(options[:socket_timeout]) do
-
8
sock = new(host, port)
-
8
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
-
8
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
-
8
sock.options = { :host => host, :port => port }.merge(options)
-
8
sock
-
end
-
end
-
-
1
def readfull(count)
-
202
value = ''
-
202
begin
-
207
loop do
-
207
value << read_nonblock(count - value.bytesize)
-
202
break if value.bytesize == count
-
end
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
-
5
if IO.select([self], nil, nil, options[:socket_timeout])
-
5
retry
-
else
-
raise Timeout::Error, "IO timeout: #{options.inspect}"
-
end
-
end
-
202
value
-
end
-
-
end
-
end
-
1
module Dalli
-
1
VERSION = '2.5.0'
-
end
-
1
require 'rack/session/abstract/id'
-
1
require 'dalli'
-
-
1
module Rack
-
1
module Session
-
1
class Dalli < Abstract::ID
-
1
attr_reader :pool, :mutex
-
-
1
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
-
:namespace => 'rack:session',
-
:memcache_server => 'localhost:11211'
-
-
1
def initialize(app, options={})
-
9
super
-
9
@mutex = Mutex.new
-
9
mserv = @default_options[:memcache_server]
-
90
mopts = @default_options.reject{|k,v| !DEFAULT_OPTIONS.include? k }
-
9
@pool = options[:cache] || ::Dalli::Client.new(mserv, mopts)
-
end
-
-
1
def generate_sid
-
loop do
-
sid = super
-
break sid unless @pool.get(sid)
-
end
-
end
-
-
1
def get_session(env, sid)
-
15
with_lock(env, [nil, {}]) do
-
15
unless sid and session = @pool.get(sid)
-
10
sid, session = generate_sid, {}
-
10
unless @pool.add(sid, session)
-
raise "Session collision on '#{sid.inspect}'"
-
end
-
end
-
15
[sid, session]
-
end
-
end
-
-
1
def set_session(env, session_id, new_session, options)
-
13
expiry = options[:expire_after]
-
13
expiry = expiry.nil? ? 0 : expiry + 1
-
-
13
with_lock(env, false) do
-
13
@pool.set session_id, new_session, expiry
-
13
session_id
-
end
-
end
-
-
1
def destroy_session(env, session_id, options)
-
2
with_lock(env) do
-
2
@pool.delete(session_id)
-
2
generate_sid unless options[:drop]
-
end
-
end
-
-
1
def with_lock(env, default=nil)
-
30
@mutex.lock if env['rack.multithread']
-
30
yield
-
rescue ::Dalli::DalliError, Errno::ECONNREFUSED
-
raise if $!.message =~ /undefined class/
-
if $VERBOSE
-
warn "#{self} is unable to find memcached server."
-
warn $!.inspect
-
end
-
default
-
ensure
-
30
@mutex.unlock if @mutex.locked?
-
end
-
-
end
-
end
-
end
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
##
-
## an implementation of eRuby
-
##
-
## ex.
-
## input = <<'END'
-
## <ul>
-
## <% for item in @list %>
-
## <li><%= item %>
-
## <%== item %></li>
-
## <% end %>
-
## </ul>
-
## END
-
## list = ['<aaa>', 'b&b', '"ccc"']
-
## eruby = Erubis::Eruby.new(input)
-
## puts "--- code ---"
-
## puts eruby.src
-
## puts "--- result ---"
-
## context = Erubis::Context.new() # or new(:list=>list)
-
## context[:list] = list
-
## puts eruby.evaluate(context)
-
##
-
## result:
-
## --- source ---
-
## _buf = ''; _buf << '<ul>
-
## '; for item in @list
-
## _buf << ' <li>'; _buf << ( item ).to_s; _buf << '
-
## '; _buf << ' '; _buf << Erubis::XmlHelper.escape_xml( item ); _buf << '</li>
-
## '; end
-
## _buf << '</ul>
-
## ';
-
## _buf.to_s
-
## --- result ---
-
## <ul>
-
## <li><aaa>
-
## <aaa></li>
-
## <li>b&b
-
## b&b</li>
-
## <li>"ccc"
-
## "ccc"</li>
-
## </ul>
-
##
-
-
-
1
module Erubis
-
1
VERSION = ('$Release: 2.7.0 $' =~ /([.\d]+)/) && $1
-
end
-
-
1
require 'erubis/engine'
-
#require 'erubis/generator'
-
#require 'erubis/converter'
-
#require 'erubis/evaluator'
-
#require 'erubis/error'
-
#require 'erubis/context'
-
#requier 'erubis/util'
-
1
require 'erubis/helper'
-
1
require 'erubis/enhancer'
-
#require 'erubis/tiny'
-
1
require 'erubis/engine/eruby'
-
#require 'erubis/engine/enhanced' # enhanced eruby engines
-
#require 'erubis/engine/optimized' # generates optimized ruby code
-
#require 'erubis/engine/ephp'
-
#require 'erubis/engine/ec'
-
#require 'erubis/engine/ejava'
-
#require 'erubis/engine/escheme'
-
#require 'erubis/engine/eperl'
-
#require 'erubis/engine/ejavascript'
-
-
1
require 'erubis/local-setting'
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
-
1
module Erubis
-
-
-
##
-
## context object for Engine#evaluate
-
##
-
## ex.
-
## template = <<'END'
-
## Hello <%= @user %>!
-
## <% for item in @list %>
-
## - <%= item %>
-
## <% end %>
-
## END
-
##
-
## context = Erubis::Context.new(:user=>'World', :list=>['a','b','c'])
-
## # or
-
## # context = Erubis::Context.new
-
## # context[:user] = 'World'
-
## # context[:list] = ['a', 'b', 'c']
-
##
-
## eruby = Erubis::Eruby.new(template)
-
## print eruby.evaluate(context)
-
##
-
1
class Context
-
1
include Enumerable
-
-
1
def initialize(hash=nil)
-
hash.each do |name, value|
-
self[name] = value
-
end if hash
-
end
-
-
1
def [](key)
-
return instance_variable_get("@#{key}")
-
end
-
-
1
def []=(key, value)
-
return instance_variable_set("@#{key}", value)
-
end
-
-
1
def keys
-
return instance_variables.collect { |name| name[1..-1] }
-
end
-
-
1
def each
-
instance_variables.each do |name|
-
key = name[1..-1]
-
value = instance_variable_get(name)
-
yield(key, value)
-
end
-
end
-
-
1
def to_hash
-
hash = {}
-
self.keys.each { |key| hash[key] = self[key] }
-
return hash
-
end
-
-
1
def update(context_or_hash)
-
arg = context_or_hash
-
if arg.is_a?(Hash)
-
arg.each do |key, val|
-
self[key] = val
-
end
-
else
-
arg.instance_variables.each do |varname|
-
key = varname[1..-1]
-
val = arg.instance_variable_get(varname)
-
self[key] = val
-
end
-
end
-
end
-
-
end
-
-
-
end
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
1
require 'erubis/util'
-
-
1
module Erubis
-
-
-
##
-
## convert
-
##
-
1
module Converter
-
-
1
attr_accessor :preamble, :postamble, :escape
-
-
1
def self.supported_properties # :nodoc:
-
return [
-
[:preamble, nil, "preamble (no preamble when false)"],
-
[:postamble, nil, "postamble (no postamble when false)"],
-
[:escape, nil, "escape expression or not in default"],
-
]
-
end
-
-
1
def init_converter(properties={})
-
714
@preamble = properties[:preamble]
-
714
@postamble = properties[:postamble]
-
714
@escape = properties[:escape]
-
end
-
-
## convert input string into target language
-
1
def convert(input)
-
714
codebuf = "" # or []
-
714
@preamble.nil? ? add_preamble(codebuf) : (@preamble && (codebuf << @preamble))
-
714
convert_input(codebuf, input)
-
714
@postamble.nil? ? add_postamble(codebuf) : (@postamble && (codebuf << @postamble))
-
714
@_proc = nil # clear cached proc object
-
714
return codebuf # or codebuf.join()
-
end
-
-
1
protected
-
-
##
-
## detect spaces at beginning of line
-
##
-
1
def detect_spaces_at_bol(text, is_bol)
-
320
lspace = nil
-
320
if text.empty?
-
81
lspace = "" if is_bol
-
elsif text[-1] == ?\n
-
40
lspace = ""
-
else
-
199
rindex = text.rindex(?\n)
-
199
if rindex
-
68
s = text[rindex+1..-1]
-
68
if s =~ /\A[ \t]*\z/
-
68
lspace = s
-
#text = text[0..rindex]
-
68
text[rindex+1..-1] = ''
-
end
-
else
-
131
if is_bol && text =~ /\A[ \t]*\z/
-
#lspace = text
-
#text = nil
-
83
lspace = text.dup
-
83
text[0..-1] = ''
-
end
-
end
-
end
-
320
return lspace
-
end
-
-
##
-
## (abstract) convert input to code
-
##
-
1
def convert_input(codebuf, input)
-
not_implemented
-
end
-
-
end
-
-
-
1
module Basic
-
end
-
-
-
##
-
## basic converter which supports '<% ... %>' notation.
-
##
-
1
module Basic::Converter
-
1
include Erubis::Converter
-
-
1
def self.supported_properties # :nodoc:
-
return [
-
[:pattern, '<% %>', "embed pattern"],
-
[:trim, true, "trim spaces around <% ... %>"],
-
]
-
end
-
-
1
attr_accessor :pattern, :trim
-
-
1
def init_converter(properties={})
-
714
super(properties)
-
714
@pattern = properties[:pattern]
-
714
@trim = properties[:trim] != false
-
end
-
-
1
protected
-
-
## return regexp of pattern to parse eRuby script
-
1
def pattern_regexp(pattern)
-
1
@prefix, @postfix = pattern.split() # '<% %>' => '<%', '%>'
-
#return /(.*?)(^[ \t]*)?#{@prefix}(=+|\#)?(.*?)-?#{@postfix}([ \t]*\r?\n)?/m
-
#return /(^[ \t]*)?#{@prefix}(=+|\#)?(.*?)-?#{@postfix}([ \t]*\r?\n)?/m
-
1
return /#{@prefix}(=+|-|\#|%)?(.*?)([-=])?#{@postfix}([ \t]*\r?\n)?/m
-
end
-
1
module_function :pattern_regexp
-
-
#DEFAULT_REGEXP = /(.*?)(^[ \t]*)?<%(=+|\#)?(.*?)-?%>([ \t]*\r?\n)?/m
-
#DEFAULT_REGEXP = /(^[ \t]*)?<%(=+|\#)?(.*?)-?%>([ \t]*\r?\n)?/m
-
#DEFAULT_REGEXP = /<%(=+|\#)?(.*?)-?%>([ \t]*\r?\n)?/m
-
1
DEFAULT_REGEXP = pattern_regexp('<% %>')
-
-
1
public
-
-
1
def convert_input(src, input)
-
714
pat = @pattern
-
714
regexp = pat.nil? || pat == '<% %>' ? DEFAULT_REGEXP : pattern_regexp(pat)
-
714
pos = 0
-
714
is_bol = true # is beginning of line
-
714
input.scan(regexp) do |indicator, code, tailch, rspace|
-
1203
match = Regexp.last_match()
-
1203
len = match.begin(0) - pos
-
1203
text = input[pos, len]
-
1203
pos = match.end(0)
-
1203
ch = indicator ? indicator[0] : nil
-
1203
lspace = ch == ?= ? nil : detect_spaces_at_bol(text, is_bol)
-
1203
is_bol = rspace ? true : false
-
1203
add_text(src, text) if text && !text.empty?
-
## * when '<%= %>', do nothing
-
## * when '<% %>' or '<%# %>', delete spaces iff only spaces are around '<% %>'
-
1203
if ch == ?= # <%= %>
-
883
rspace = nil if tailch && !tailch.empty?
-
883
add_text(src, lspace) if lspace
-
883
add_expr(src, code, indicator)
-
883
add_text(src, rspace) if rspace
-
elsif ch == ?\# # <%# %>
-
n = code.count("\n") + (rspace ? 1 : 0)
-
if @trim && lspace && rspace
-
add_stmt(src, "\n" * n)
-
else
-
add_text(src, lspace) if lspace
-
add_stmt(src, "\n" * n)
-
add_text(src, rspace) if rspace
-
end
-
elsif ch == ?% # <%% %>
-
s = "#{lspace}#{@prefix||='<%'}#{code}#{tailch}#{@postfix||='%>'}#{rspace}"
-
add_text(src, s)
-
else # <% %>
-
320
if @trim && lspace && rspace
-
237
add_stmt(src, "#{lspace}#{code}#{rspace}")
-
else
-
83
add_text(src, lspace) if lspace
-
83
add_stmt(src, code)
-
83
add_text(src, rspace) if rspace
-
end
-
end
-
end
-
#rest = $' || input # ruby1.8
-
714
rest = pos == 0 ? input : input[pos..-1] # ruby1.9
-
714
add_text(src, rest)
-
end
-
-
## add expression code to src
-
1
def add_expr(src, code, indicator)
-
883
case indicator
-
when '='
-
882
@escape ? add_expr_escaped(src, code) : add_expr_literal(src, code)
-
when '=='
-
1
@escape ? add_expr_literal(src, code) : add_expr_escaped(src, code)
-
when '==='
-
add_expr_debug(src, code)
-
end
-
end
-
-
end
-
-
-
1
module PI
-
end
-
-
##
-
## Processing Instructions (PI) converter for XML.
-
## this class converts '<?rb ... ?>' and '${...}' notation.
-
##
-
1
module PI::Converter
-
1
include Erubis::Converter
-
-
1
def self.desc # :nodoc:
-
"use processing instructions (PI) instead of '<% %>'"
-
end
-
-
1
def self.supported_properties # :nodoc:
-
return [
-
[:trim, true, "trim spaces around <% ... %>"],
-
[:pi, 'rb', "PI (Processing Instrunctions) name"],
-
[:embchar, '@', "char for embedded expression pattern('@{...}@')"],
-
[:pattern, '<% %>', "embed pattern"],
-
]
-
end
-
-
1
attr_accessor :pi, :prefix
-
-
1
def init_converter(properties={})
-
super(properties)
-
@trim = properties.fetch(:trim, true)
-
@pi = properties[:pi] if properties[:pi]
-
@embchar = properties[:embchar] || '@'
-
@pattern = properties[:pattern]
-
@pattern = '<% %>' if @pattern.nil? #|| @pattern == true
-
end
-
-
1
def convert(input)
-
code = super(input)
-
return @header || @footer ? "#{@header}#{code}#{@footer}" : code
-
end
-
-
1
protected
-
-
1
def convert_input(codebuf, input)
-
unless @regexp
-
@pi ||= 'e'
-
ch = Regexp.escape(@embchar)
-
if @pattern
-
left, right = @pattern.split(' ')
-
@regexp = /<\?#{@pi}(?:-(\w+))?(\s.*?)\?>([ \t]*\r?\n)?|#{ch}(!*)?\{(.*?)\}#{ch}|#{left}(=+)(.*?)#{right}/m
-
else
-
@regexp = /<\?#{@pi}(?:-(\w+))?(\s.*?)\?>([ \t]*\r?\n)?|#{ch}(!*)?\{(.*?)\}#{ch}/m
-
end
-
end
-
#
-
is_bol = true
-
pos = 0
-
input.scan(@regexp) do |pi_arg, stmt, rspace,
-
indicator1, expr1, indicator2, expr2|
-
match = Regexp.last_match
-
len = match.begin(0) - pos
-
text = input[pos, len]
-
pos = match.end(0)
-
lspace = stmt ? detect_spaces_at_bol(text, is_bol) : nil
-
is_bol = stmt && rspace ? true : false
-
add_text(codebuf, text) # unless text.empty?
-
#
-
if stmt
-
if @trim && lspace && rspace
-
add_pi_stmt(codebuf, "#{lspace}#{stmt}#{rspace}", pi_arg)
-
else
-
add_text(codebuf, lspace) if lspace
-
add_pi_stmt(codebuf, stmt, pi_arg)
-
add_text(codebuf, rspace) if rspace
-
end
-
else
-
add_pi_expr(codebuf, expr1 || expr2, indicator1 || indicator2)
-
end
-
end
-
#rest = $' || input # ruby1.8
-
rest = pos == 0 ? input : input[pos..-1] # ruby1.9
-
add_text(codebuf, rest)
-
end
-
-
#--
-
#def convert_input(codebuf, input)
-
# parse_stmts(codebuf, input)
-
# #parse_stmts2(codebuf, input)
-
#end
-
#
-
#def parse_stmts(codebuf, input)
-
# #regexp = pattern_regexp(@pattern)
-
# @pi ||= 'e'
-
# @stmt_pattern ||= /<\?#{@pi}(?:-(\w+))?(\s.*?)\?>([ \t]*\r?\n)?/m
-
# is_bol = true
-
# pos = 0
-
# input.scan(@stmt_pattern) do |pi_arg, code, rspace|
-
# match = Regexp.last_match
-
# len = match.begin(0) - pos
-
# text = input[pos, len]
-
# pos = match.end(0)
-
# lspace = detect_spaces_at_bol(text, is_bol)
-
# is_bol = rspace ? true : false
-
# parse_exprs(codebuf, text) # unless text.empty?
-
# if @trim && lspace && rspace
-
# add_pi_stmt(codebuf, "#{lspace}#{code}#{rspace}", pi_arg)
-
# else
-
# add_text(codebuf, lspace)
-
# add_pi_stmt(codebuf, code, pi_arg)
-
# add_text(codebuf, rspace)
-
# end
-
# end
-
# rest = $' || input
-
# parse_exprs(codebuf, rest)
-
#end
-
#
-
#def parse_exprs(codebuf, input)
-
# unless @expr_pattern
-
# ch = Regexp.escape(@embchar)
-
# if @pattern
-
# left, right = @pattern.split(' ')
-
# @expr_pattern = /#{ch}(!*)?\{(.*?)\}#{ch}|#{left}(=+)(.*?)#{right}/
-
# else
-
# @expr_pattern = /#{ch}(!*)?\{(.*?)\}#{ch}/
-
# end
-
# end
-
# pos = 0
-
# input.scan(@expr_pattern) do |indicator1, code1, indicator2, code2|
-
# indicator = indicator1 || indicator2
-
# code = code1 || code2
-
# match = Regexp.last_match
-
# len = match.begin(0) - pos
-
# text = input[pos, len]
-
# pos = match.end(0)
-
# add_text(codebuf, text) # unless text.empty?
-
# add_pi_expr(codebuf, code, indicator)
-
# end
-
# rest = $' || input
-
# add_text(codebuf, rest)
-
#end
-
#++
-
-
1
def add_pi_stmt(codebuf, code, pi_arg) # :nodoc:
-
case pi_arg
-
when nil ; add_stmt(codebuf, code)
-
when 'header' ; @header = code
-
when 'footer' ; @footer = code
-
when 'comment'; add_stmt(codebuf, "\n" * code.count("\n"))
-
when 'value' ; add_expr_literal(codebuf, code)
-
else ; add_stmt(codebuf, code)
-
end
-
end
-
-
1
def add_pi_expr(codebuf, code, indicator) # :nodoc:
-
case indicator
-
when nil, '', '==' # @{...}@ or <%== ... %>
-
@escape == false ? add_expr_literal(codebuf, code) : add_expr_escaped(codebuf, code)
-
when '!', '=' # @!{...}@ or <%= ... %>
-
@escape == false ? add_expr_escaped(codebuf, code) : add_expr_literal(codebuf, code)
-
when '!!', '===' # @!!{...}@ or <%=== ... %>
-
add_expr_debug(codebuf, code)
-
else
-
# ignore
-
end
-
end
-
-
end
-
-
-
end
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
-
1
require 'erubis/generator'
-
1
require 'erubis/converter'
-
1
require 'erubis/evaluator'
-
1
require 'erubis/context'
-
-
-
1
module Erubis
-
-
-
##
-
## (abstract) abstract engine class.
-
## subclass must include evaluator and converter module.
-
##
-
1
class Engine
-
#include Evaluator
-
#include Converter
-
#include Generator
-
-
1
def initialize(input=nil, properties={})
-
#@input = input
-
714
init_generator(properties)
-
714
init_converter(properties)
-
714
init_evaluator(properties)
-
714
@src = convert(input) if input
-
end
-
-
-
##
-
## convert input string and set it to @src
-
##
-
1
def convert!(input)
-
@src = convert(input)
-
end
-
-
-
##
-
## load file, write cache file, and return engine object.
-
## this method create code cache file automatically.
-
## cachefile name can be specified with properties[:cachename],
-
## or filname + 'cache' is used as default.
-
##
-
1
def self.load_file(filename, properties={})
-
cachename = properties[:cachename] || (filename + '.cache')
-
properties[:filename] = filename
-
timestamp = File.mtime(filename)
-
if test(?f, cachename) && timestamp == File.mtime(cachename)
-
engine = self.new(nil, properties)
-
engine.src = File.read(cachename)
-
else
-
input = File.open(filename, 'rb') {|f| f.read }
-
engine = self.new(input, properties)
-
tmpname = cachename + rand().to_s[1,8]
-
File.open(tmpname, 'wb') {|f| f.write(engine.src) }
-
File.rename(tmpname, cachename)
-
File.utime(timestamp, timestamp, cachename)
-
end
-
engine.src.untaint # ok?
-
return engine
-
end
-
-
-
##
-
## helper method to convert and evaluate input text with context object.
-
## context may be Binding, Hash, or Object.
-
##
-
1
def process(input, context=nil, filename=nil)
-
code = convert(input)
-
filename ||= '(erubis)'
-
if context.is_a?(Binding)
-
return eval(code, context, filename)
-
else
-
context = Context.new(context) if context.is_a?(Hash)
-
return context.instance_eval(code, filename)
-
end
-
end
-
-
-
##
-
## helper method evaluate Proc object with contect object.
-
## context may be Binding, Hash, or Object.
-
##
-
1
def process_proc(proc_obj, context=nil, filename=nil)
-
if context.is_a?(Binding)
-
filename ||= '(erubis)'
-
return eval(proc_obj, context, filename)
-
else
-
context = Context.new(context) if context.is_a?(Hash)
-
return context.instance_eval(&proc_obj)
-
end
-
end
-
-
-
end # end of class Engine
-
-
-
##
-
## (abstract) base engine class for Eruby, Eperl, Ejava, and so on.
-
## subclass must include generator.
-
##
-
1
class Basic::Engine < Engine
-
1
include Evaluator
-
1
include Basic::Converter
-
1
include Generator
-
end
-
-
-
1
class PI::Engine < Engine
-
1
include Evaluator
-
1
include PI::Converter
-
1
include Generator
-
end
-
-
-
end
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
-
1
module Erubis
-
-
-
##
-
## switch '<%= ... %>' to escaped and '<%== ... %>' to unescaped
-
##
-
## ex.
-
## class XmlEruby < Eruby
-
## include EscapeEnhancer
-
## end
-
##
-
## this is language-indenedent.
-
##
-
1
module EscapeEnhancer
-
-
1
def self.desc # :nodoc:
-
"switch '<%= %>' to escaped and '<%== %>' to unescaped"
-
end
-
-
#--
-
#def self.included(klass)
-
# klass.class_eval <<-END
-
# alias _add_expr_literal add_expr_literal
-
# alias _add_expr_escaped add_expr_escaped
-
# alias add_expr_literal _add_expr_escaped
-
# alias add_expr_escaped _add_expr_literal
-
# END
-
#end
-
#++
-
-
1
def add_expr(src, code, indicator)
-
case indicator
-
when '='
-
@escape ? add_expr_literal(src, code) : add_expr_escaped(src, code)
-
when '=='
-
@escape ? add_expr_escaped(src, code) : add_expr_literal(src, code)
-
when '==='
-
add_expr_debug(src, code)
-
end
-
end
-
-
end
-
-
-
#--
-
## (obsolete)
-
#module FastEnhancer
-
#end
-
#++
-
-
-
##
-
## use $stdout instead of string
-
##
-
## this is only for Eruby.
-
##
-
1
module StdoutEnhancer
-
-
1
def self.desc # :nodoc:
-
"use $stdout instead of array buffer or string buffer"
-
end
-
-
1
def add_preamble(src)
-
src << "#{@bufvar} = $stdout;"
-
end
-
-
1
def add_postamble(src)
-
src << "\n''\n"
-
end
-
-
end
-
-
-
##
-
## use print statement instead of '_buf << ...'
-
##
-
## this is only for Eruby.
-
##
-
1
module PrintOutEnhancer
-
-
1
def self.desc # :nodoc:
-
"use print statement instead of '_buf << ...'"
-
end
-
-
1
def add_preamble(src)
-
end
-
-
1
def add_text(src, text)
-
src << " print '#{escape_text(text)}';" unless text.empty?
-
end
-
-
1
def add_expr_literal(src, code)
-
src << " print((#{code}).to_s);"
-
end
-
-
1
def add_expr_escaped(src, code)
-
src << " print #{escaped_expr(code)};"
-
end
-
-
1
def add_postamble(src)
-
src << "\n" unless src[-1] == ?\n
-
end
-
-
end
-
-
-
##
-
## enable print function
-
##
-
## Notice: use Eruby#evaluate() and don't use Eruby#result()
-
## to be enable print function.
-
##
-
## this is only for Eruby.
-
##
-
1
module PrintEnabledEnhancer
-
-
1
def self.desc # :nodoc:
-
"enable to use print function in '<% %>'"
-
end
-
-
1
def add_preamble(src)
-
src << "@_buf = "
-
super
-
end
-
-
1
def print(*args)
-
args.each do |arg|
-
@_buf << arg.to_s
-
end
-
end
-
-
1
def evaluate(context=nil)
-
_src = @src
-
if context.is_a?(Hash)
-
context.each do |key, val| instance_variable_set("@#{key}", val) end
-
elsif context
-
context.instance_variables.each do |name|
-
instance_variable_set(name, context.instance_variable_get(name))
-
end
-
end
-
return instance_eval(_src, (@filename || '(erubis)'))
-
end
-
-
end
-
-
-
##
-
## return array instead of string
-
##
-
## this is only for Eruby.
-
##
-
1
module ArrayEnhancer
-
-
1
def self.desc # :nodoc:
-
"return array instead of string"
-
end
-
-
1
def add_preamble(src)
-
src << "#{@bufvar} = [];"
-
end
-
-
1
def add_postamble(src)
-
src << "\n" unless src[-1] == ?\n
-
src << "#{@bufvar}\n"
-
end
-
-
end
-
-
-
##
-
## use an Array object as buffer (included in Eruby by default)
-
##
-
## this is only for Eruby.
-
##
-
1
module ArrayBufferEnhancer
-
-
1
def self.desc # :nodoc:
-
"use an Array object for buffering (included in Eruby class)"
-
end
-
-
1
def add_preamble(src)
-
src << "_buf = [];"
-
end
-
-
1
def add_postamble(src)
-
src << "\n" unless src[-1] == ?\n
-
src << "_buf.join\n"
-
end
-
-
end
-
-
-
##
-
## use String class for buffering
-
##
-
## this is only for Eruby.
-
##
-
1
module StringBufferEnhancer
-
-
1
def self.desc # :nodoc:
-
"use a String object for buffering"
-
end
-
-
1
def add_preamble(src)
-
src << "#{@bufvar} = '';"
-
end
-
-
1
def add_postamble(src)
-
src << "\n" unless src[-1] == ?\n
-
src << "#{@bufvar}.to_s\n"
-
end
-
-
end
-
-
-
##
-
## use StringIO class for buffering
-
##
-
## this is only for Eruby.
-
##
-
1
module StringIOEnhancer # :nodoc:
-
-
1
def self.desc # :nodoc:
-
"use a StringIO object for buffering"
-
end
-
-
1
def add_preamble(src)
-
src << "#{@bufvar} = StringIO.new;"
-
end
-
-
1
def add_postamble(src)
-
src << "\n" unless src[-1] == ?\n
-
src << "#{@bufvar}.string\n"
-
end
-
-
end
-
-
-
##
-
## set buffer variable name to '_erbout' as well as '_buf'
-
##
-
## this is only for Eruby.
-
##
-
1
module ErboutEnhancer
-
-
1
def self.desc # :nodoc:
-
"set '_erbout = _buf = \"\";' to be compatible with ERB."
-
end
-
-
1
def add_preamble(src)
-
src << "_erbout = #{@bufvar} = '';"
-
end
-
-
1
def add_postamble(src)
-
src << "\n" unless src[-1] == ?\n
-
src << "#{@bufvar}.to_s\n"
-
end
-
-
end
-
-
-
##
-
## remove text and leave code, especially useful when debugging.
-
##
-
## ex.
-
## $ erubis -s -E NoText file.eruby | more
-
##
-
## this is language independent.
-
##
-
1
module NoTextEnhancer
-
-
1
def self.desc # :nodoc:
-
"remove text and leave code (useful when debugging)"
-
end
-
-
1
def add_text(src, text)
-
src << ("\n" * text.count("\n"))
-
if text[-1] != ?\n
-
text =~ /^(.*?)\z/
-
src << (' ' * $1.length)
-
end
-
end
-
-
end
-
-
-
##
-
## remove code and leave text, especially useful when validating HTML tags.
-
##
-
## ex.
-
## $ erubis -s -E NoCode file.eruby | tidy -errors
-
##
-
## this is language independent.
-
##
-
1
module NoCodeEnhancer
-
-
1
def self.desc # :nodoc:
-
"remove code and leave text (useful when validating HTML)"
-
end
-
-
1
def add_preamble(src)
-
end
-
-
1
def add_postamble(src)
-
end
-
-
1
def add_text(src, text)
-
src << text
-
end
-
-
1
def add_expr(src, code, indicator)
-
src << "\n" * code.count("\n")
-
end
-
-
1
def add_stmt(src, code)
-
src << "\n" * code.count("\n")
-
end
-
-
end
-
-
-
##
-
## get convert faster, but spaces around '<%...%>' are not trimmed.
-
##
-
## this is language-independent.
-
##
-
1
module SimplifyEnhancer
-
-
1
def self.desc # :nodoc:
-
"get convert faster but leave spaces around '<% %>'"
-
end
-
-
#DEFAULT_REGEXP = /(^[ \t]*)?<%(=+|\#)?(.*?)-?%>([ \t]*\r?\n)?/m
-
1
SIMPLE_REGEXP = /<%(=+|\#)?(.*?)-?%>/m
-
-
1
def convert(input)
-
src = ""
-
add_preamble(src)
-
#regexp = pattern_regexp(@pattern)
-
pos = 0
-
input.scan(SIMPLE_REGEXP) do |indicator, code|
-
match = Regexp.last_match
-
index = match.begin(0)
-
text = input[pos, index - pos]
-
pos = match.end(0)
-
add_text(src, text)
-
if !indicator # <% %>
-
add_stmt(src, code)
-
elsif indicator[0] == ?\# # <%# %>
-
n = code.count("\n")
-
add_stmt(src, "\n" * n)
-
else # <%= %>
-
add_expr(src, code, indicator)
-
end
-
end
-
#rest = $' || input # ruby1.8
-
rest = pos == 0 ? input : input[pos..-1] # ruby1.9
-
add_text(src, rest)
-
add_postamble(src)
-
return src
-
end
-
-
end
-
-
-
##
-
## enable to use other embedded expression pattern (default is '\[= =\]').
-
##
-
## notice! this is an experimental. spec may change in the future.
-
##
-
## ex.
-
## input = <<END
-
## <% for item in list %>
-
## <%= item %> : <%== item %>
-
## [= item =] : [== item =]
-
## <% end %>
-
## END
-
##
-
## class BiPatternEruby
-
## include BiPatternEnhancer
-
## end
-
## eruby = BiPatternEruby.new(input, :bipattern=>'\[= =\]')
-
## list = ['<a>', 'b&b', '"c"']
-
## print eruby.result(binding())
-
##
-
## ## output
-
## <a> : <a>
-
## <a> : <a>
-
## b&b : b&b
-
## b&b : b&b
-
## "c" : "c"
-
## "c" : "c"
-
##
-
## this is language independent.
-
##
-
1
module BiPatternEnhancer
-
-
1
def self.desc # :nodoc:
-
"another embedded expression pattern (default '\[= =\]')."
-
end
-
-
1
def initialize(input, properties={})
-
self.bipattern = properties[:bipattern] # or '\$\{ \}'
-
super
-
end
-
-
## when pat is nil then '\[= =\]' is used
-
1
def bipattern=(pat) # :nodoc:
-
@bipattern = pat || '\[= =\]'
-
pre, post = @bipattern.split()
-
@bipattern_regexp = /(.*?)#{pre}(=*)(.*?)#{post}/m
-
end
-
-
1
def add_text(src, text)
-
return unless text
-
m = nil
-
text.scan(@bipattern_regexp) do |txt, indicator, code|
-
m = Regexp.last_match
-
super(src, txt)
-
add_expr(src, code, '=' + indicator)
-
end
-
#rest = $' || text # ruby1.8
-
rest = m ? text[m.end(0)..-1] : text # ruby1.9
-
super(src, rest)
-
end
-
-
end
-
-
-
##
-
## regards lines starting with '^[ \t]*%' as program code
-
##
-
## in addition you can specify prefix character (default '%')
-
##
-
## this is language-independent.
-
##
-
1
module PrefixedLineEnhancer
-
-
1
def self.desc # :nodoc:
-
"regard lines matched to '^[ \t]*%' as program code"
-
end
-
-
1
def init_generator(properties={})
-
super
-
@prefixchar = properties[:prefixchar]
-
end
-
-
1
def add_text(src, text)
-
unless @prefixrexp
-
@prefixchar ||= '%'
-
@prefixrexp = Regexp.compile("^([ \\t]*)\\#{@prefixchar}(.*?\\r?\\n)")
-
end
-
pos = 0
-
text2 = ''
-
text.scan(@prefixrexp) do
-
space = $1
-
line = $2
-
space, line = '', $1 unless $2
-
match = Regexp.last_match
-
len = match.begin(0) - pos
-
str = text[pos, len]
-
pos = match.end(0)
-
if text2.empty?
-
text2 = str
-
else
-
text2 << str
-
end
-
if line[0, 1] == @prefixchar
-
text2 << space << line
-
else
-
super(src, text2)
-
text2 = ''
-
add_stmt(src, space + line)
-
end
-
end
-
#rest = pos == 0 ? text : $' # ruby1.8
-
rest = pos == 0 ? text : text[pos..-1] # ruby1.9
-
unless text2.empty?
-
text2 << rest if rest
-
rest = text2
-
end
-
super(src, rest)
-
end
-
-
end
-
-
-
##
-
## regards lines starting with '%' as program code
-
##
-
## this is for compatibility to eruby and ERB.
-
##
-
## this is language-independent.
-
##
-
1
module PercentLineEnhancer
-
1
include PrefixedLineEnhancer
-
-
1
def self.desc # :nodoc:
-
"regard lines starting with '%' as program code"
-
end
-
-
#--
-
#def init_generator(properties={})
-
# super
-
# @prefixchar = '%'
-
# @prefixrexp = /^\%(.*?\r?\n)/
-
#end
-
#++
-
-
1
def add_text(src, text)
-
unless @prefixrexp
-
@prefixchar = '%'
-
@prefixrexp = /^\%(.*?\r?\n)/
-
end
-
super(src, text)
-
end
-
-
end
-
-
-
##
-
## [experimental] allow header and footer in eRuby script
-
##
-
## ex.
-
## ====================
-
## ## without header and footer
-
## $ cat ex1.eruby
-
## <% def list_items(list) %>
-
## <% for item in list %>
-
## <li><%= item %></li>
-
## <% end %>
-
## <% end %>
-
##
-
## $ erubis -s ex1.eruby
-
## _buf = []; def list_items(list)
-
## ; for item in list
-
## ; _buf << '<li>'; _buf << ( item ).to_s; _buf << '</li>
-
## '; end
-
## ; end
-
## ;
-
## _buf.join
-
##
-
## ## with header and footer
-
## $ cat ex2.eruby
-
## <!--#header:
-
## def list_items(list)
-
## #-->
-
## <% for item in list %>
-
## <li><%= item %></li>
-
## <% end %>
-
## <!--#footer:
-
## end
-
## #-->
-
##
-
## $ erubis -s -c HeaderFooterEruby ex4.eruby
-
##
-
## def list_items(list)
-
## _buf = []; _buf << '
-
## '; for item in list
-
## ; _buf << '<li>'; _buf << ( item ).to_s; _buf << '</li>
-
## '; end
-
## ; _buf << '
-
## ';
-
## _buf.join
-
## end
-
##
-
## ====================
-
##
-
## this is language-independent.
-
##
-
1
module HeaderFooterEnhancer
-
-
1
def self.desc # :nodoc:
-
"allow header/footer in document (ex. '<!--#header: #-->')"
-
end
-
-
1
HEADER_FOOTER_PATTERN = /(.*?)(^[ \t]*)?<!--\#(\w+):(.*?)\#-->([ \t]*\r?\n)?/m
-
-
1
def add_text(src, text)
-
m = nil
-
text.scan(HEADER_FOOTER_PATTERN) do |txt, lspace, word, content, rspace|
-
m = Regexp.last_match
-
flag_trim = @trim && lspace && rspace
-
super(src, txt)
-
content = "#{lspace}#{content}#{rspace}" if flag_trim
-
super(src, lspace) if !flag_trim && lspace
-
instance_variable_set("@#{word}", content)
-
super(src, rspace) if !flag_trim && rspace
-
end
-
#rest = $' || text # ruby1.8
-
rest = m ? text[m.end(0)..-1] : text # ruby1.9
-
super(src, rest)
-
end
-
-
1
attr_accessor :header, :footer
-
-
1
def convert(input)
-
source = super
-
return @src = "#{@header}#{source}#{@footer}"
-
end
-
-
end
-
-
-
##
-
## delete indentation of HTML.
-
##
-
## this is language-independent.
-
##
-
1
module DeleteIndentEnhancer
-
-
1
def self.desc # :nodoc:
-
"delete indentation of HTML."
-
end
-
-
1
def convert_input(src, input)
-
input = input.gsub(/^[ \t]+</, '<')
-
super(src, input)
-
end
-
-
end
-
-
-
##
-
## convert "<h1><%=title%></h1>" into "_buf << %Q`<h1>#{title}</h1>`"
-
##
-
## this is only for Eruby.
-
##
-
1
module InterpolationEnhancer
-
-
1
def self.desc # :nodoc:
-
"convert '<p><%=text%></p>' into '_buf << %Q`<p>\#{text}</p>`'"
-
end
-
-
1
def convert_input(src, input)
-
pat = @pattern
-
regexp = pat.nil? || pat == '<% %>' ? Basic::Converter::DEFAULT_REGEXP : pattern_regexp(pat)
-
pos = 0
-
is_bol = true # is beginning of line
-
str = ''
-
input.scan(regexp) do |indicator, code, tailch, rspace|
-
match = Regexp.last_match()
-
len = match.begin(0) - pos
-
text = input[pos, len]
-
pos = match.end(0)
-
ch = indicator ? indicator[0] : nil
-
lspace = ch == ?= ? nil : detect_spaces_at_bol(text, is_bol)
-
is_bol = rspace ? true : false
-
_add_text_to_str(str, text)
-
## * when '<%= %>', do nothing
-
## * when '<% %>' or '<%# %>', delete spaces iff only spaces are around '<% %>'
-
if ch == ?= # <%= %>
-
rspace = nil if tailch && !tailch.empty?
-
str << lspace if lspace
-
add_expr(str, code, indicator)
-
str << rspace if rspace
-
elsif ch == ?\# # <%# %>
-
n = code.count("\n") + (rspace ? 1 : 0)
-
if @trim && lspace && rspace
-
add_text(src, str)
-
str = ''
-
add_stmt(src, "\n" * n)
-
else
-
str << lspace if lspace
-
add_text(src, str)
-
str = ''
-
add_stmt(src, "\n" * n)
-
str << rspace if rspace
-
end
-
else # <% %>
-
if @trim && lspace && rspace
-
add_text(src, str)
-
str = ''
-
add_stmt(src, "#{lspace}#{code}#{rspace}")
-
else
-
str << lspace if lspace
-
add_text(src, str)
-
str = ''
-
add_stmt(src, code)
-
str << rspace if rspace
-
end
-
end
-
end
-
#rest = $' || input # ruby1.8
-
rest = pos == 0 ? input : input[pos..-1] # ruby1.9
-
_add_text_to_str(str, rest)
-
add_text(src, str)
-
end
-
-
1
def add_text(src, text)
-
return if !text || text.empty?
-
#src << " _buf << %Q`" << text << "`;"
-
if text[-1] == ?\n
-
text[-1] = "\\n"
-
src << " #{@bufvar} << %Q`#{text}`\n"
-
else
-
src << " #{@bufvar} << %Q`#{text}`;"
-
end
-
end
-
-
1
def _add_text_to_str(str, text)
-
return if !text || text.empty?
-
str << text.gsub(/[`\#\\]/, '\\\\\&')
-
end
-
-
1
def add_expr_escaped(str, code)
-
str << "\#{#{escaped_expr(code)}}"
-
end
-
-
1
def add_expr_literal(str, code)
-
str << "\#{#{code}}"
-
end
-
-
end
-
-
-
end
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
1
module Erubis
-
-
-
##
-
## base error class
-
##
-
1
class ErubisError < StandardError
-
end
-
-
-
##
-
## raised when method or function is not supported
-
##
-
1
class NotSupportedError < ErubisError
-
end
-
-
-
end
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
1
require 'erubis/error'
-
1
require 'erubis/context'
-
-
-
1
module Erubis
-
-
1
EMPTY_BINDING = binding()
-
-
-
##
-
## evaluate code
-
##
-
1
module Evaluator
-
-
1
def self.supported_properties # :nodoc:
-
return []
-
end
-
-
1
attr_accessor :src, :filename
-
-
1
def init_evaluator(properties)
-
714
@filename = properties[:filename]
-
end
-
-
1
def result(*args)
-
raise NotSupportedError.new("evaluation of code except Ruby is not supported.")
-
end
-
-
1
def evaluate(*args)
-
raise NotSupportedError.new("evaluation of code except Ruby is not supported.")
-
end
-
-
end
-
-
-
##
-
## evaluator for Ruby
-
##
-
1
module RubyEvaluator
-
1
include Evaluator
-
-
1
def self.supported_properties # :nodoc:
-
list = Evaluator.supported_properties
-
return list
-
end
-
-
## eval(@src) with binding object
-
1
def result(_binding_or_hash=TOPLEVEL_BINDING)
-
_arg = _binding_or_hash
-
if _arg.is_a?(Hash)
-
_b = binding()
-
eval _arg.collect{|k,v| "#{k} = _arg[#{k.inspect}]; "}.join, _b
-
elsif _arg.is_a?(Binding)
-
_b = _arg
-
elsif _arg.nil?
-
_b = binding()
-
else
-
raise ArgumentError.new("#{self.class.name}#result(): argument should be Binding or Hash but passed #{_arg.class.name} object.")
-
end
-
return eval(@src, _b, (@filename || '(erubis'))
-
end
-
-
## invoke context.instance_eval(@src)
-
1
def evaluate(_context=Context.new)
-
6
_context = Context.new(_context) if _context.is_a?(Hash)
-
#return _context.instance_eval(@src, @filename || '(erubis)')
-
#@_proc ||= eval("proc { #{@src} }", Erubis::EMPTY_BINDING, @filename || '(erubis)')
-
6
@_proc ||= eval("proc { #{@src} }", binding(), @filename || '(erubis)')
-
6
return _context.instance_eval(&@_proc)
-
end
-
-
## if object is an Class or Module then define instance method to it,
-
## else define singleton method to it.
-
1
def def_method(object, method_name, filename=nil)
-
m = object.is_a?(Module) ? :module_eval : :instance_eval
-
object.__send__(m, "def #{method_name}; #{@src}; end", filename || @filename || '(erubis)')
-
end
-
-
-
end
-
-
-
end
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
1
require 'erubis/util'
-
-
1
module Erubis
-
-
-
##
-
## code generator, called by Converter module
-
##
-
1
module Generator
-
-
1
def self.supported_properties() # :nodoc:
-
return [
-
[:escapefunc, nil, "escape function name"],
-
]
-
end
-
-
1
attr_accessor :escapefunc
-
-
1
def init_generator(properties={})
-
714
@escapefunc = properties[:escapefunc]
-
end
-
-
-
## (abstract) escape text string
-
##
-
## ex.
-
## def escape_text(text)
-
## return text.dump
-
## # or return "'" + text.gsub(/['\\]/, '\\\\\&') + "'"
-
## end
-
1
def escape_text(text)
-
not_implemented
-
end
-
-
## return escaped expression code (ex. 'h(...)' or 'htmlspecialchars(...)')
-
1
def escaped_expr(code)
-
code.strip!
-
return "#{@escapefunc}(#{code})"
-
end
-
-
## (abstract) add @preamble to src
-
1
def add_preamble(src)
-
not_implemented
-
end
-
-
## (abstract) add text string to src
-
1
def add_text(src, text)
-
not_implemented
-
end
-
-
## (abstract) add statement code to src
-
1
def add_stmt(src, code)
-
not_implemented
-
end
-
-
## (abstract) add expression literal code to src. this is called by add_expr().
-
1
def add_expr_literal(src, code)
-
not_implemented
-
end
-
-
## (abstract) add escaped expression code to src. this is called by add_expr().
-
1
def add_expr_escaped(src, code)
-
not_implemented
-
end
-
-
## (abstract) add expression code to src for debug. this is called by add_expr().
-
1
def add_expr_debug(src, code)
-
not_implemented
-
end
-
-
## (abstract) add @postamble to src
-
1
def add_postamble(src)
-
not_implemented
-
end
-
-
-
end
-
-
-
end
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
-
1
module Erubis
-
-
##
-
## helper for xml
-
##
-
1
module XmlHelper
-
-
1
module_function
-
-
1
ESCAPE_TABLE = {
-
'&' => '&',
-
'<' => '<',
-
'>' => '>',
-
'"' => '"',
-
"'" => ''',
-
}
-
-
1
def escape_xml(value)
-
value.to_s.gsub(/[&<>"]/) { |s| ESCAPE_TABLE[s] } # or /[&<>"']/
-
#value.to_s.gsub(/[&<>"]/) { ESCAPE_TABLE[$&] }
-
end
-
-
1
def escape_xml2(value)
-
return value.to_s.gsub(/\&/,'&').gsub(/</,'<').gsub(/>/,'>').gsub(/"/,'"')
-
end
-
-
1
alias h escape_xml
-
1
alias html_escape escape_xml
-
-
1
def url_encode(str)
-
return str.gsub(/[^-_.a-zA-Z0-9]+/) { |s|
-
s.unpack('C*').collect { |i| "%%%02X" % i }.join
-
}
-
end
-
-
1
alias u url_encode
-
-
end
-
-
-
end
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
##
-
## you can add site-local settings here.
-
## this files is required by erubis.rb
-
##
-
##
-
## $Release: 2.7.0 $
-
## copyright(c) 2006-2011 kuwata-lab.com all rights reserved.
-
##
-
-
1
module Kernel
-
-
##
-
## raise NotImplementedError
-
##
-
1
def not_implemented #:doc:
-
backtrace = caller()
-
method_name = (backtrace.shift =~ /`(\w+)'$/) && $1
-
mesg = "class #{self.class.name} must implement abstract method '#{method_name}()'."
-
#mesg = "#{self.class.name}##{method_name}() is not implemented."
-
err = NotImplementedError.new mesg
-
err.set_backtrace backtrace
-
raise err
-
end
-
1
private :not_implemented
-
-
end
-
1
require 'i18n/version'
-
1
require 'i18n/exceptions'
-
1
require 'i18n/interpolate/ruby'
-
-
1
module I18n
-
1
autoload :Backend, 'i18n/backend'
-
1
autoload :Config, 'i18n/config'
-
1
autoload :Gettext, 'i18n/gettext'
-
1
autoload :Locale, 'i18n/locale'
-
1
autoload :Tests, 'i18n/tests'
-
-
1
RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback, :format, :cascade, :throw, :raise, :rescue_format]
-
1
RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
-
-
extend Module.new {
-
# Gets I18n configuration object.
-
1
def config
-
13276
Thread.current[:i18n_config] ||= I18n::Config.new
-
end
-
-
# Sets I18n configuration object.
-
1
def config=(value)
-
3154
Thread.current[:i18n_config] = value
-
end
-
-
# Write methods which delegates to the configuration object
-
1
%w(locale backend default_locale available_locales default_separator
-
exception_handler load_path).each do |method|
-
7
module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
-
def #{method}
-
config.#{method}
-
end
-
-
def #{method}=(value)
-
config.#{method} = (value)
-
end
-
DELEGATORS
-
end
-
-
# Tells the backend to reload translations. Used in situations like the
-
# Rails development environment. Backends can implement whatever strategy
-
# is useful.
-
1
def reload!
-
176
config.backend.reload!
-
end
-
-
# Translates, pluralizes and interpolates a given key using a given locale,
-
# scope, and default, as well as interpolation values.
-
#
-
# *LOOKUP*
-
#
-
# Translation data is organized as a nested hash using the upper-level keys
-
# as namespaces. <em>E.g.</em>, ActionView ships with the translation:
-
# <tt>:date => {:formats => {:short => "%b %d"}}</tt>.
-
#
-
# Translations can be looked up at any level of this hash using the key argument
-
# and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
-
# returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>.
-
#
-
# Key can be either a single key or a dot-separated key (both Strings and Symbols
-
# work). <em>E.g.</em>, the short format can be looked up using both:
-
# I18n.t 'date.formats.short'
-
# I18n.t :'date.formats.short'
-
#
-
# Scope can be either a single key, a dot-separated key or an array of keys
-
# or dot-separated keys. Keys and scopes can be combined freely. So these
-
# examples will all look up the same short date format:
-
# I18n.t 'date.formats.short'
-
# I18n.t 'formats.short', :scope => 'date'
-
# I18n.t 'short', :scope => 'date.formats'
-
# I18n.t 'short', :scope => %w(date formats)
-
#
-
# *INTERPOLATION*
-
#
-
# Translations can contain interpolation variables which will be replaced by
-
# values passed to #translate as part of the options hash, with the keys matching
-
# the interpolation variable names.
-
#
-
# <em>E.g.</em>, with a translation <tt>:foo => "foo %{bar}"</tt> the option
-
# value for the key +bar+ will be interpolated into the translation:
-
# I18n.t :foo, :bar => 'baz' # => 'foo baz'
-
#
-
# *PLURALIZATION*
-
#
-
# Translation data can contain pluralized translations. Pluralized translations
-
# are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
-
#
-
# Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
-
# pluralization rules. Other algorithms can be supported by custom backends.
-
#
-
# This returns the singular version of a pluralized translation:
-
# I18n.t :foo, :count => 1 # => 'Foo'
-
#
-
# These both return the plural version of a pluralized translation:
-
# I18n.t :foo, :count => 0 # => 'Foos'
-
# I18n.t :foo, :count => 2 # => 'Foos'
-
#
-
# The <tt>:count</tt> option can be used both for pluralization and interpolation.
-
# <em>E.g.</em>, with the translation
-
# <tt>:foo => ['%{count} foo', '%{count} foos']</tt>, count will
-
# be interpolated to the pluralized translation:
-
# I18n.t :foo, :count => 1 # => '1 foo'
-
#
-
# *DEFAULTS*
-
#
-
# This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found:
-
# I18n.t :foo, :default => 'default'
-
#
-
# This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
-
# translation for <tt>:foo</tt> was found:
-
# I18n.t :foo, :default => :bar
-
#
-
# Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
-
# or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found.
-
# I18n.t :foo, :default => [:bar, 'default']
-
#
-
# *BULK LOOKUP*
-
#
-
# This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>.
-
# I18n.t [:foo, :bar]
-
#
-
# Can be used with dot-separated nested keys:
-
# I18n.t [:'baz.foo', :'baz.bar']
-
#
-
# Which is the same as using a scope option:
-
# I18n.t [:foo, :bar], :scope => :baz
-
#
-
# *LAMBDAS*
-
#
-
# Both translations and defaults can be given as Ruby lambdas. Lambdas will be
-
# called and passed the key and options.
-
#
-
# E.g. assuming the key <tt>:salutation</tt> resolves to:
-
# lambda { |key, options| options[:gender] == 'm' ? "Mr. %{options[:name]}" : "Mrs. %{options[:name]}" }
-
#
-
# Then <tt>I18n.t(:salutation, :gender => 'w', :name => 'Smith') will result in "Mrs. Smith".
-
#
-
# It is recommended to use/implement lambdas in an "idempotent" way. E.g. when
-
# a cache layer is put in front of I18n.translate it will generate a cache key
-
# from the argument values passed to #translate. Therefor your lambdas should
-
# always return the same translations/values per unique combination of argument
-
# values.
-
1
def translate(*args)
-
1639
options = args.last.is_a?(Hash) ? args.pop : {}
-
1639
key = args.shift
-
1639
backend = config.backend
-
1639
locale = options.delete(:locale) || config.locale
-
1639
handling = options.delete(:throw) && :throw || options.delete(:raise) && :raise # TODO deprecate :raise
-
-
1639
raise I18n::ArgumentError if key.is_a?(String) && key.empty?
-
-
1639
result = catch(:exception) do
-
1639
if key.is_a?(Array)
-
key.map { |k| backend.translate(locale, k, options) }
-
else
-
1639
backend.translate(locale, key, options)
-
end
-
end
-
1639
result.is_a?(MissingTranslation) ? handle_exception(handling, result, locale, key, options) : result
-
end
-
1
alias :t :translate
-
-
# Wrapper for <tt>translate</tt> that adds <tt>:raise => true</tt>. With
-
# this option, if no translation is found, it will raise <tt>I18n::MissingTranslationData</tt>
-
1
def translate!(key, options={})
-
translate(key, options.merge(:raise => true))
-
end
-
1
alias :t! :translate!
-
-
# Transliterates UTF-8 characters to ASCII. By default this method will
-
# transliterate only Latin strings to an ASCII approximation:
-
#
-
# I18n.transliterate("Ærøskøbing")
-
# # => "AEroskobing"
-
#
-
# I18n.transliterate("日本語")
-
# # => "???"
-
#
-
# It's also possible to add support for per-locale transliterations. I18n
-
# expects transliteration rules to be stored at
-
# <tt>i18n.transliterate.rule</tt>.
-
#
-
# Transliteration rules can either be a Hash or a Proc. Procs must accept a
-
# single string argument. Hash rules inherit the default transliteration
-
# rules, while Procs do not.
-
#
-
# *Examples*
-
#
-
# Setting a Hash in <locale>.yml:
-
#
-
# i18n:
-
# transliterate:
-
# rule:
-
# ü: "ue"
-
# ö: "oe"
-
#
-
# Setting a Hash using Ruby:
-
#
-
# store_translations(:de, :i18n => {
-
# :transliterate => {
-
# :rule => {
-
# "ü" => "ue",
-
# "ö" => "oe"
-
# }
-
# }
-
# )
-
#
-
# Setting a Proc:
-
#
-
# translit = lambda {|string| MyTransliterator.transliterate(string) }
-
# store_translations(:xx, :i18n => {:transliterate => {:rule => translit})
-
#
-
# Transliterating strings:
-
#
-
# I18n.locale = :en
-
# I18n.transliterate("Jürgen") # => "Jurgen"
-
# I18n.locale = :de
-
# I18n.transliterate("Jürgen") # => "Juergen"
-
# I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen"
-
# I18n.transliterate("Jürgen", :locale => :de) # => "Juergen"
-
1
def transliterate(*args)
-
options = args.pop if args.last.is_a?(Hash)
-
key = args.shift
-
locale = options && options.delete(:locale) || config.locale
-
handling = options && (options.delete(:throw) && :throw || options.delete(:raise) && :raise)
-
replacement = options && options.delete(:replacement)
-
config.backend.transliterate(locale, key, replacement)
-
rescue I18n::ArgumentError => exception
-
handle_exception(handling, exception, locale, key, options || {})
-
end
-
-
# Localizes certain objects, such as dates and numbers to local formatting.
-
1
def localize(object, options = {})
-
8
locale = options.delete(:locale) || config.locale
-
8
format = options.delete(:format) || :default
-
8
config.backend.localize(locale, object, format, options)
-
end
-
1
alias :l :localize
-
-
# Executes block with given I18n.locale set.
-
1
def with_locale(tmp_locale = nil)
-
if tmp_locale
-
current_locale = self.locale
-
self.locale = tmp_locale
-
end
-
yield
-
ensure
-
self.locale = current_locale if tmp_locale
-
end
-
-
# Merges the given locale, key and scope into a single array of keys.
-
# Splits keys that contain dots into multiple keys. Makes sure all
-
# keys are Symbols.
-
1
def normalize_keys(locale, key, scope, separator = nil)
-
1646
separator ||= I18n.default_separator
-
-
1646
keys = []
-
1646
keys.concat normalize_key(locale, separator)
-
1646
keys.concat normalize_key(scope, separator)
-
1646
keys.concat normalize_key(key, separator)
-
1646
keys
-
end
-
-
# making these private until Ruby 1.9.2 can send to protected methods again
-
# see http://redmine.ruby-lang.org/repositories/revision/ruby-19?rev=24280
-
1
private
-
-
# Any exceptions thrown in translate will be sent to the @@exception_handler
-
# which can be a Symbol, a Proc or any other Object unless they're forced to
-
# be raised or thrown (MissingTranslation).
-
#
-
# If exception_handler is a Symbol then it will simply be sent to I18n as
-
# a method call. A Proc will simply be called. In any other case the
-
# method #call will be called on the exception_handler object.
-
#
-
# Examples:
-
#
-
# I18n.exception_handler = :default_exception_handler # this is the default
-
# I18n.default_exception_handler(exception, locale, key, options) # will be called like this
-
#
-
# I18n.exception_handler = lambda { |*args| ... } # a lambda
-
# I18n.exception_handler.call(exception, locale, key, options) # will be called like this
-
#
-
# I18n.exception_handler = I18nExceptionHandler.new # an object
-
# I18n.exception_handler.call(exception, locale, key, options) # will be called like this
-
1
def handle_exception(handling, exception, locale, key, options)
-
54
case handling
-
when :raise
-
raise(exception.respond_to?(:to_exception) ? exception.to_exception : exception)
-
when :throw
-
47
throw :exception, exception
-
else
-
7
case handler = options[:exception_handler] || config.exception_handler
-
when Symbol
-
send(handler, exception, locale, key, options)
-
else
-
7
handler.call(exception, locale, key, options)
-
end
-
end
-
end
-
-
1
def normalize_key(key, separator)
-
4940
normalized_key_cache[separator][key] ||=
-
case key
-
when Array
-
3
key.map { |k| normalize_key(k, separator) }.flatten
-
else
-
98
keys = key.to_s.split(separator)
-
98
keys.delete('')
-
346
keys.map! { |k| k.to_sym }
-
98
keys
-
end
-
end
-
-
1
def normalized_key_cache
-
4941
@normalized_key_cache ||= Hash.new { |h,k| h[k] = {} }
-
end
-
-
# DEPRECATED. Use I18n.normalize_keys instead.
-
1
def normalize_translation_keys(locale, key, scope, separator = nil)
-
puts "I18n.normalize_translation_keys is deprecated. Please use the class I18n.normalize_keys instead."
-
normalize_keys(locale, key, scope, separator)
-
end
-
-
# DEPRECATED. Please use the I18n::ExceptionHandler class instead.
-
1
def default_exception_handler(exception, locale, key, options)
-
puts "I18n.default_exception_handler is deprecated. Please use the class I18n::ExceptionHandler instead " +
-
"(an instance of which is set to I18n.exception_handler by default)."
-
exception.is_a?(MissingTranslation) ? exception.message : raise(exception)
-
end
-
1
}
-
end
-
1
module I18n
-
1
module Backend
-
1
autoload :Base, 'i18n/backend/base'
-
1
autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
-
1
autoload :Cache, 'i18n/backend/cache'
-
1
autoload :Cascade, 'i18n/backend/cascade'
-
1
autoload :Chain, 'i18n/backend/chain'
-
1
autoload :Fallbacks, 'i18n/backend/fallbacks'
-
1
autoload :Flatten, 'i18n/backend/flatten'
-
1
autoload :Gettext, 'i18n/backend/gettext'
-
1
autoload :KeyValue, 'i18n/backend/key_value'
-
1
autoload :Memoize, 'i18n/backend/memoize'
-
1
autoload :Metadata, 'i18n/backend/metadata'
-
1
autoload :Pluralization, 'i18n/backend/pluralization'
-
1
autoload :Simple, 'i18n/backend/simple'
-
1
autoload :Transliterator, 'i18n/backend/transliterator'
-
end
-
end
-
1
require 'yaml'
-
1
require 'i18n/core_ext/hash'
-
1
require 'i18n/core_ext/kernel/surpress_warnings'
-
-
1
module I18n
-
1
module Backend
-
1
module Base
-
1
include I18n::Backend::Transliterator
-
-
# Accepts a list of paths to translation files. Loads translations from
-
# plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
-
# for details.
-
1
def load_translations(*filenames)
-
180
filenames = I18n.load_path if filenames.empty?
-
900
filenames.flatten.each { |filename| load_file(filename) }
-
end
-
-
# This method receives a locale, a data hash and options for storing translations.
-
# Should be implemented
-
1
def store_translations(locale, data, options = {})
-
raise NotImplementedError
-
end
-
-
1
def translate(locale, key, options = {})
-
1639
raise InvalidLocale.new(locale) unless locale
-
1639
entry = key && lookup(locale, key, options[:scope], options)
-
-
1639
if options.empty?
-
128
entry = resolve(locale, key, entry, options)
-
else
-
1511
count, default = options.values_at(:count, :default)
-
1511
values = options.except(*RESERVED_KEYS)
-
1511
entry = entry.nil? && default ?
-
default(locale, key, default, options) : resolve(locale, key, entry, options)
-
end
-
-
1639
throw(:exception, I18n::MissingTranslation.new(locale, key, options)) if entry.nil?
-
1585
entry = entry.dup if entry.is_a?(String)
-
-
1585
entry = pluralize(locale, entry, count) if count
-
1585
entry = interpolate(locale, entry, values) if values
-
1585
entry
-
end
-
-
# Acts the same as +strftime+, but uses a localized version of the
-
# format string. Takes a key from the date/time formats translations as
-
# a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
-
1
def localize(locale, object, format = :default, options = {})
-
8
raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
-
-
8
if Symbol === format
-
8
key = format
-
8
type = object.respond_to?(:sec) ? 'time' : 'date'
-
8
options = options.merge(:raise => true, :object => object, :locale => locale)
-
8
format = I18n.t(:"#{type}.formats.#{key}", options)
-
end
-
-
# format = resolve(locale, object, format, options)
-
8
format = format.to_s.gsub(/%[aAbBp]/) do |match|
-
8
case match
-
when '%a' then I18n.t(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
-
when '%A' then I18n.t(:"date.day_names", :locale => locale, :format => format)[object.wday]
-
2
when '%b' then I18n.t(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
-
6
when '%B' then I18n.t(:"date.month_names", :locale => locale, :format => format)[object.mon]
-
when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format) if object.respond_to? :hour
-
end
-
end
-
-
8
object.strftime(format)
-
end
-
-
# Returns an array of locales for which translations are available
-
# ignoring the reserved translation meta data key :i18n.
-
1
def available_locales
-
raise NotImplementedError
-
end
-
-
1
def reload!
-
176
@skip_syntax_deprecation = false
-
end
-
-
1
protected
-
-
# The method which actually looks up for the translation in the store.
-
1
def lookup(locale, key, scope = [], options = {})
-
raise NotImplementedError
-
end
-
-
# Evaluates defaults.
-
# If given subject is an Array, it walks the array and returns the
-
# first translation that can be resolved. Otherwise it tries to resolve
-
# the translation directly.
-
1
def default(locale, object, subject, options = {})
-
205
options = options.dup.reject { |key, value| key == :default }
-
67
case subject
-
when Array
-
subject.each do |item|
-
111
result = resolve(locale, object, item, options) and return result
-
64
end and nil
-
else
-
3
resolve(locale, object, subject, options)
-
end
-
end
-
-
# Resolves a translation.
-
# If the given subject is a Symbol, it will be translated with the
-
# given options. If it is a Proc then it will be evaluated. All other
-
# subjects will be returned directly.
-
1
def resolve(locale, object, subject, options = {})
-
1692
return subject if options[:resolve] == false
-
1692
result = catch(:exception) do
-
1692
case subject
-
when Symbol
-
52
I18n.translate(subject, options.merge(:locale => locale, :throw => true))
-
when Proc
-
6
date_or_time = options.delete(:object) || object
-
6
resolve(locale, object, subject.call(date_or_time, options))
-
else
-
1634
subject
-
end
-
end
-
1692
result unless result.is_a?(MissingTranslation)
-
end
-
-
# Picks a translation from an array according to English pluralization
-
# rules. It will pick the first translation if count is not equal to 1
-
# and the second translation if it is equal to 1. Other backends can
-
# implement more flexible or complex pluralization rules.
-
1
def pluralize(locale, entry, count)
-
400
return entry unless entry.is_a?(Hash) && count
-
-
312
key = :zero if count == 0 && entry.has_key?(:zero)
-
312
key ||= count == 1 ? :one : :other
-
312
raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
-
312
entry[key]
-
end
-
-
# Interpolates values into a given string.
-
#
-
# interpolate "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
-
# # => "file test.txt opened by %{user}"
-
1
def interpolate(locale, string, values = {})
-
1459
if string.is_a?(::String) && !values.empty?
-
411
I18n.interpolate(string, values)
-
else
-
1048
string
-
end
-
end
-
-
# Loads a single translations file by delegating to #load_rb or
-
# #load_yml depending on the file extension and directly merges the
-
# data to the existing translations. Raises I18n::UnknownFileType
-
# for all other file extensions.
-
1
def load_file(filename)
-
720
type = File.extname(filename).tr('.', '').downcase
-
720
raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
-
720
data = send(:"load_#{type}", filename)
-
720
raise InvalidLocaleData.new(filename) unless data.is_a?(Hash)
-
1440
data.each { |locale, d| store_translations(locale, d || {}) }
-
end
-
-
# Loads a plain Ruby translations file. eval'ing the file must yield
-
# a Hash containing translation data with locales as toplevel keys.
-
1
def load_rb(filename)
-
eval(IO.read(filename), binding, filename)
-
end
-
-
# Loads a YAML translations file. The data must have locales as
-
# toplevel keys.
-
1
def load_yml(filename)
-
720
begin
-
720
YAML.load_file(filename)
-
rescue TypeError
-
nil
-
rescue SyntaxError
-
nil
-
end
-
end
-
end
-
end
-
end
-
1
module I18n
-
1
module Backend
-
# A simple backend that reads translations from YAML files and stores them in
-
# an in-memory hash. Relies on the Base backend.
-
#
-
# The implementation is provided by a Implementation module allowing to easily
-
# extend Simple backend's behavior by including modules. E.g.:
-
#
-
# module I18n::Backend::Pluralization
-
# def pluralize(*args)
-
# # extended pluralization logic
-
# super
-
# end
-
# end
-
#
-
# I18n::Backend::Simple.include(I18n::Backend::Pluralization)
-
1
class Simple
-
3
(class << self; self; end).class_eval { public :include }
-
-
1
module Implementation
-
1
include Base
-
-
1
def initialized?
-
1816
@initialized ||= false
-
end
-
-
# Stores translations for the given locale in memory.
-
# This uses a deep merge for the translations hash, so existing
-
# translations will be overwritten by new ones only at the deepest
-
# level of the hash.
-
1
def store_translations(locale, data, options = {})
-
1521
locale = locale.to_sym
-
1521
translations[locale] ||= {}
-
1521
data = data.deep_symbolize_keys
-
1521
translations[locale].deep_merge!(data)
-
end
-
-
# Get available locales from the translations hash
-
1
def available_locales
-
177
init_translations unless initialized?
-
177
translations.inject([]) do |locales, (locale, data)|
-
531
locales << locale unless (data.keys - [:i18n]).empty?
-
531
locales
-
end
-
end
-
-
# Clean up translations hash and set initialized to false on reload!
-
1
def reload!
-
176
@initialized = false
-
176
@translations = nil
-
176
super
-
end
-
-
1
protected
-
-
1
def init_translations
-
180
load_translations
-
180
@initialized = true
-
end
-
-
1
def translations
-
4858
@translations ||= {}
-
end
-
-
# Looks up a translation from the translations hash. Returns nil if
-
# eiher key is nil, or locale, scope or key do not exist as a key in the
-
# nested translations hash. Splits keys or scopes containing dots
-
# into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
-
# <tt>%w(currency format)</tt>.
-
1
def lookup(locale, key, scope = [], options = {})
-
1639
init_translations unless initialized?
-
1639
keys = I18n.normalize_keys(locale, key, scope, options[:separator])
-
-
1639
keys.inject(translations) do |result, _key|
-
6007
_key = _key.to_sym
-
6007
return nil unless result.is_a?(Hash) && result.has_key?(_key)
-
5886
result = result[_key]
-
5886
result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
-
5886
result
-
end
-
end
-
end
-
-
1
include Implementation
-
end
-
end
-
end
-
# encoding: utf-8
-
1
module I18n
-
1
module Backend
-
1
module Transliterator
-
1
DEFAULT_REPLACEMENT_CHAR = "?"
-
-
# Given a locale and a UTF-8 string, return the locale's ASCII
-
# approximation for the string.
-
1
def transliterate(locale, string, replacement = nil)
-
@transliterators ||= {}
-
@transliterators[locale] ||= Transliterator.get I18n.t(:'i18n.transliterate.rule',
-
:locale => locale, :resolve => false, :default => {})
-
@transliterators[locale].transliterate(string, replacement)
-
end
-
-
# Get a transliterator instance.
-
1
def self.get(rule = nil)
-
if !rule || rule.kind_of?(Hash)
-
HashTransliterator.new(rule)
-
elsif rule.kind_of? Proc
-
ProcTransliterator.new(rule)
-
else
-
raise I18n::ArgumentError, "Transliteration rule must be a proc or a hash."
-
end
-
end
-
-
# A transliterator which accepts a Proc as its transliteration rule.
-
1
class ProcTransliterator
-
1
def initialize(rule)
-
@rule = rule
-
end
-
-
1
def transliterate(string, replacement = nil)
-
@rule.call(string)
-
end
-
end
-
-
# A transliterator which accepts a Hash of characters as its translation
-
# rule.
-
1
class HashTransliterator
-
1
DEFAULT_APPROXIMATIONS = {
-
"À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"AE",
-
"Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I",
-
"Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O",
-
"Õ"=>"O", "Ö"=>"O", "×"=>"x", "Ø"=>"O", "Ù"=>"U", "Ú"=>"U", "Û"=>"U",
-
"Ü"=>"U", "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "à"=>"a", "á"=>"a", "â"=>"a",
-
"ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c", "è"=>"e", "é"=>"e",
-
"ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", "ï"=>"i", "ð"=>"d",
-
"ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o", "ö"=>"o", "ø"=>"o",
-
"ù"=>"u", "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y", "þ"=>"th", "ÿ"=>"y",
-
"Ā"=>"A", "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A", "ą"=>"a", "Ć"=>"C",
-
"ć"=>"c", "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c", "Č"=>"C", "č"=>"c",
-
"Ď"=>"D", "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E", "ē"=>"e", "Ĕ"=>"E",
-
"ĕ"=>"e", "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e", "Ě"=>"E", "ě"=>"e",
-
"Ĝ"=>"G", "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G", "ġ"=>"g", "Ģ"=>"G",
-
"ģ"=>"g", "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h", "Ĩ"=>"I", "ĩ"=>"i",
-
"Ī"=>"I", "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I", "į"=>"i", "İ"=>"I",
-
"ı"=>"i", "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j", "Ķ"=>"K", "ķ"=>"k",
-
"ĸ"=>"k", "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l", "Ľ"=>"L", "ľ"=>"l",
-
"Ŀ"=>"L", "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N", "ń"=>"n", "Ņ"=>"N",
-
"ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG", "ŋ"=>"ng",
-
"Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o", "Œ"=>"OE",
-
"œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R", "ř"=>"r",
-
"Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s", "Š"=>"S",
-
"š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T", "ŧ"=>"t",
-
"Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u", "Ů"=>"U",
-
"ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W", "ŵ"=>"w",
-
"Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z", "ż"=>"z",
-
"Ž"=>"Z", "ž"=>"z"
-
}
-
-
1
def initialize(rule = nil)
-
@rule = rule
-
add DEFAULT_APPROXIMATIONS
-
add rule if rule
-
end
-
-
1
def transliterate(string, replacement = nil)
-
string.gsub(/[^\x00-\x7f]/u) do |char|
-
approximations[char] || replacement || DEFAULT_REPLACEMENT_CHAR
-
end
-
end
-
-
1
private
-
-
1
def approximations
-
@approximations ||= {}
-
end
-
-
# Add transliteration rules to the approximations hash.
-
1
def add(hash)
-
hash.keys.each {|key| hash[key.to_s] = hash.delete(key).to_s}
-
approximations.merge! hash
-
end
-
end
-
end
-
end
-
end
-
1
module I18n
-
1
class Config
-
# The only configuration value that is not global and scoped to thread is :locale.
-
# It defaults to the default_locale.
-
1
def locale
-
3512
@locale ||= default_locale
-
end
-
-
# Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
-
1
def locale=(locale)
-
63
@locale = locale.to_sym rescue nil
-
end
-
-
# Returns the current backend. Defaults to +Backend::Simple+.
-
1
def backend
-
2803
@@backend ||= Backend::Simple.new
-
end
-
-
# Sets the current backend. Used to set a custom backend.
-
1
def backend=(backend)
-
2
@@backend = backend
-
end
-
-
# Returns the current default locale. Defaults to :'en'
-
1
def default_locale
-
1886
@@default_locale ||= :en
-
end
-
-
# Sets the current default locale. Used to set a custom default locale.
-
1
def default_locale=(locale)
-
@@default_locale = locale.to_sym rescue nil
-
end
-
-
# Returns an array of locales for which translations are available.
-
# Unless you explicitely set these through I18n.available_locales=
-
# the call will be delegated to the backend.
-
1
def available_locales
-
177
@@available_locales ||= nil
-
177
@@available_locales || backend.available_locales
-
end
-
-
# Sets the available locales.
-
1
def available_locales=(locales)
-
@@available_locales = Array(locales).map { |locale| locale.to_sym }
-
@@available_locales = nil if @@available_locales.empty?
-
end
-
-
# Returns the current default scope separator. Defaults to '.'
-
1
def default_separator
-
1646
@@default_separator ||= '.'
-
end
-
-
# Sets the current default scope separator.
-
1
def default_separator=(separator)
-
@@default_separator = separator
-
end
-
-
# Return the current exception handler. Defaults to :default_exception_handler.
-
1
def exception_handler
-
7
@@exception_handler ||= ExceptionHandler.new
-
end
-
-
# Sets the exception handler.
-
1
def exception_handler=(exception_handler)
-
@@exception_handler = exception_handler
-
end
-
-
# Allow clients to register paths providing translation data sources. The
-
# backend defines acceptable sources.
-
#
-
# E.g. the provided SimpleBackend accepts a list of paths to translation
-
# files which are either named *.rb and contain plain Ruby Hashes or are
-
# named *.yml and contain YAML data. So for the SimpleBackend clients may
-
# register translation files like this:
-
# I18n.load_path << 'path/to/locale/en.yml'
-
1
def load_path
-
184
@@load_path ||= []
-
end
-
-
# Sets the load path instance. Custom implementations are expected to
-
# behave like a Ruby Array.
-
1
def load_path=(load_path)
-
@@load_path = load_path
-
end
-
end
-
end
-
1
class Hash
-
def slice(*keep_keys)
-
h = {}
-
keep_keys.each { |key| h[key] = fetch(key) }
-
h
-
1
end unless Hash.method_defined?(:slice)
-
-
def except(*less_keys)
-
slice(*keys - less_keys)
-
1
end unless Hash.method_defined?(:except)
-
-
def deep_symbolize_keys
-
inject({}) { |result, (key, value)|
-
value = value.deep_symbolize_keys if value.is_a?(Hash)
-
result[(key.to_sym rescue key) || key] = value
-
result
-
}
-
1
end unless Hash.method_defined?(:deep_symbolize_keys)
-
-
# deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
-
1
MERGER = proc do |key, v1, v2|
-
2
Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2
-
end
-
-
def deep_merge!(data)
-
6
merge!(data, &MERGER)
-
1
end unless Hash.method_defined?(:deep_merge!)
-
end
-
-
1
module Kernel
-
1
def suppress_warnings
-
original_verbosity = $VERBOSE
-
$VERBOSE = nil
-
result = yield
-
$VERBOSE = original_verbosity
-
result
-
end
-
end
-
1
module I18n
-
# Handles exceptions raised in the backend. All exceptions except for
-
# MissingTranslationData exceptions are re-thrown. When a MissingTranslationData
-
# was caught the handler returns an error message string containing the key/scope.
-
# Note that the exception handler is not called when the option :throw was given.
-
1
class ExceptionHandler
-
include Module.new {
-
1
def call(exception, locale, key, options)
-
7
if exception.is_a?(MissingTranslation)
-
7
options[:rescue_format] == :html ? exception.html_message : exception.message
-
elsif exception.is_a?(Exception)
-
raise exception
-
else
-
throw :exception, exception
-
end
-
end
-
1
}
-
end
-
-
1
class ArgumentError < ::ArgumentError; end
-
-
1
class InvalidLocale < ArgumentError
-
1
attr_reader :locale
-
1
def initialize(locale)
-
@locale = locale
-
super "#{locale.inspect} is not a valid locale"
-
end
-
end
-
-
1
class InvalidLocaleData < ArgumentError
-
1
attr_reader :filename
-
1
def initialize(filename)
-
@filename = filename
-
super "can not load translations from #{filename}, expected it to return a hash, but does not"
-
end
-
end
-
-
1
class MissingTranslation
-
1
module Base
-
1
attr_reader :locale, :key, :options
-
-
1
def initialize(locale, key, options = nil)
-
54
@key, @locale, @options = key, locale, options.dup || {}
-
106
options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) }
-
end
-
-
1
def html_message
-
6
key = keys.last.to_s.gsub('_', ' ').gsub(/\b('?[a-z])/) { $1.capitalize }
-
3
%(<span class="translation_missing" title="translation missing: #{keys.join('.')}">#{key}</span>)
-
end
-
-
1
def keys
-
@keys ||= I18n.normalize_keys(locale, key, options[:scope]).tap do |keys|
-
7
keys << 'no key' if keys.size < 2
-
10
end
-
end
-
-
1
def message
-
4
"translation missing: #{keys.join('.')}"
-
end
-
1
alias :to_s :message
-
-
1
def to_exception
-
MissingTranslationData.new(locale, key, options)
-
end
-
end
-
-
1
include Base
-
end
-
-
1
class MissingTranslationData < ArgumentError
-
1
include MissingTranslation::Base
-
end
-
-
1
class InvalidPluralizationData < ArgumentError
-
1
attr_reader :entry, :count
-
1
def initialize(entry, count)
-
@entry, @count = entry, count
-
super "translation data #{entry.inspect} can not be used with :count => #{count}"
-
end
-
end
-
-
1
class MissingInterpolationArgument < ArgumentError
-
1
attr_reader :values, :string
-
1
def initialize(values, string)
-
@values, @string = values, string
-
super "missing interpolation argument in #{string.inspect} (#{values.inspect} given)"
-
end
-
end
-
-
1
class ReservedInterpolationKey < ArgumentError
-
1
attr_reader :key, :string
-
1
def initialize(key, string)
-
@key, @string = key, string
-
super "reserved key #{key.inspect} used in #{string.inspect}"
-
end
-
end
-
-
1
class UnknownFileType < ArgumentError
-
1
attr_reader :type, :filename
-
1
def initialize(type, filename)
-
@type, @filename = type, filename
-
super "can not load translations from #{filename}, the file type #{type} is not known"
-
end
-
end
-
end
-
# heavily based on Masao Mutoh's gettext String interpolation extension
-
# http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
-
-
1
module I18n
-
1
INTERPOLATION_PATTERN = Regexp.union(
-
/%%/,
-
/%\{(\w+)\}/, # matches placeholders like "%{foo}"
-
/%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
-
)
-
-
1
class << self
-
1
def interpolate(string, values)
-
411
raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ RESERVED_KEYS_PATTERN
-
411
raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash)
-
411
interpolate_hash(string, values)
-
end
-
-
1
def interpolate_hash(string, values)
-
411
string.gsub(INTERPOLATION_PATTERN) do |match|
-
162
if match == '%%'
-
'%'
-
else
-
162
key = ($1 || $2).to_sym
-
162
value = values.key?(key) ? values[key] : raise(MissingInterpolationArgument.new(values, string))
-
162
value = value.call(values) if value.respond_to?(:call)
-
162
$3 ? sprintf("%#{$3}", value) : value
-
end
-
end
-
end
-
end
-
end
-
1
module I18n
-
1
VERSION = "0.6.1"
-
end
-
##
-
# = JavaScript Object Notation (JSON)
-
#
-
# JSON is a lightweight data-interchange format. It is easy for us
-
# humans to read and write. Plus, equally simple for machines to generate or parse.
-
# JSON is completely language agnostic, making it the ideal interchange format.
-
#
-
# Built on two universally available structures:
-
# 1. A collection of name/value pairs. Often referred to as an _object_, hash table, record, struct, keyed list, or associative array.
-
# 2. An ordered list of values. More commonly called an _array_, vector, sequence or list.
-
#
-
# To read more about JSON visit: http://json.org
-
#
-
# == Parsing JSON
-
#
-
# To parse a JSON string received by another application or generated within
-
# your existing application:
-
#
-
# require 'json'
-
#
-
# my_hash = JSON.parse('{"hello": "goodbye"}')
-
# puts my_hash["hello"] => "goodbye"
-
#
-
# Notice the extra quotes <tt>''</tt> around the hash notation. Ruby expects
-
# the argument to be a string and can't convert objects like a hash or array.
-
#
-
# Ruby converts your string into a hash
-
#
-
# == Generating JSON
-
#
-
# Creating a JSON string for communication or serialization is
-
# just as simple.
-
#
-
# require 'json'
-
#
-
# my_hash = {:hello => "goodbye"}
-
# puts JSON.generate(my_hash) => "{\"hello\":\"goodbye\"}"
-
#
-
# Or an alternative way:
-
#
-
# require 'json'
-
# puts {:hello => "goodbye"}.to_json => "{\"hello\":\"goodbye\"}"
-
#
-
# <tt>JSON.generate</tt> only allows objects or arrays to be converted
-
# to JSON syntax. <tt>to_json</tt>, however, accepts many Ruby classes
-
# even though it acts only as a method for serialization:
-
#
-
# require 'json'
-
#
-
# 1.to_json => "1"
-
#
-
-
1
require 'json/common'
-
1
module JSON
-
1
require 'json/version'
-
-
1
begin
-
1
require 'json/ext'
-
rescue LoadError
-
require 'json/pure'
-
end
-
end
-
1
require 'json/version'
-
1
require 'json/generic_object'
-
-
1
module JSON
-
1
class << self
-
# If _object_ is string-like, parse the string and return the parsed result
-
# as a Ruby data structure. Otherwise generate a JSON text from the Ruby
-
# data structure object and return it.
-
#
-
# The _opts_ argument is passed through to generate/parse respectively. See
-
# generate and parse for their documentation.
-
1
def [](object, opts = {})
-
if object.respond_to? :to_str
-
JSON.parse(object.to_str, opts)
-
else
-
JSON.generate(object, opts)
-
end
-
end
-
-
# Returns the JSON parser class that is used by JSON. This is either
-
# JSON::Ext::Parser or JSON::Pure::Parser.
-
1
attr_reader :parser
-
-
# Set the JSON parser class _parser_ to be used by JSON.
-
1
def parser=(parser) # :nodoc:
-
1
@parser = parser
-
1
remove_const :Parser if JSON.const_defined_in?(self, :Parser)
-
1
const_set :Parser, parser
-
end
-
-
# Return the constant located at _path_. The format of _path_ has to be
-
# either ::A::B::C or A::B::C. In any case, A has to be located at the top
-
# level (absolute namespace path?). If there doesn't exist a constant at
-
# the given path, an ArgumentError is raised.
-
1
def deep_const_get(path) # :nodoc:
-
10
path.to_s.split(/::/).inject(Object) do |p, c|
-
case
-
when c.empty? then p
-
10
when JSON.const_defined_in?(p, c) then p.const_get(c)
-
else
-
begin
-
p.const_missing(c)
-
rescue NameError => e
-
raise ArgumentError, "can't get const #{path}: #{e}"
-
end
-
10
end
-
end
-
end
-
-
# Set the module _generator_ to be used by JSON.
-
1
def generator=(generator) # :nodoc:
-
1
old, $VERBOSE = $VERBOSE, nil
-
1
@generator = generator
-
1
generator_methods = generator::GeneratorMethods
-
1
for const in generator_methods.constants
-
10
klass = deep_const_get(const)
-
10
modul = generator_methods.const_get(const)
-
10
klass.class_eval do
-
10
instance_methods(false).each do |m|
-
509
m.to_s == 'to_json' and remove_method m
-
end
-
10
include modul
-
end
-
end
-
1
self.state = generator::State
-
1
const_set :State, self.state
-
1
const_set :SAFE_STATE_PROTOTYPE, State.new
-
1
const_set :FAST_STATE_PROTOTYPE, State.new(
-
:indent => '',
-
:space => '',
-
:object_nl => "",
-
:array_nl => "",
-
:max_nesting => false
-
)
-
1
const_set :PRETTY_STATE_PROTOTYPE, State.new(
-
:indent => ' ',
-
:space => ' ',
-
:object_nl => "\n",
-
:array_nl => "\n"
-
)
-
ensure
-
1
$VERBOSE = old
-
end
-
-
# Returns the JSON generator module that is used by JSON. This is
-
# either JSON::Ext::Generator or JSON::Pure::Generator.
-
1
attr_reader :generator
-
-
# Returns the JSON generator state class that is used by JSON. This is
-
# either JSON::Ext::Generator::State or JSON::Pure::Generator::State.
-
1
attr_accessor :state
-
-
# This is create identifier, which is used to decide if the _json_create_
-
# hook of a class should be called. It defaults to 'json_class'.
-
1
attr_accessor :create_id
-
end
-
1
self.create_id = 'json_class'
-
-
1
NaN = 0.0/0
-
-
1
Infinity = 1.0/0
-
-
1
MinusInfinity = -Infinity
-
-
# The base exception for JSON errors.
-
1
class JSONError < StandardError
-
1
def self.wrap(exception)
-
obj = new("Wrapped(#{exception.class}): #{exception.message.inspect}")
-
obj.set_backtrace exception.backtrace
-
obj
-
end
-
end
-
-
# This exception is raised if a parser error occurs.
-
1
class ParserError < JSONError; end
-
-
# This exception is raised if the nesting of parsed data structures is too
-
# deep.
-
1
class NestingError < ParserError; end
-
-
# :stopdoc:
-
1
class CircularDatastructure < NestingError; end
-
# :startdoc:
-
-
# This exception is raised if a generator or unparser error occurs.
-
1
class GeneratorError < JSONError; end
-
# For backwards compatibility
-
1
UnparserError = GeneratorError
-
-
# This exception is raised if the required unicode support is missing on the
-
# system. Usually this means that the iconv library is not installed.
-
1
class MissingUnicodeSupport < JSONError; end
-
-
1
module_function
-
-
# Parse the JSON document _source_ into a Ruby data structure and return it.
-
#
-
# _opts_ can have the following
-
# keys:
-
# * *max_nesting*: The maximum depth of nesting allowed in the parsed data
-
# structures. Disable depth checking with :max_nesting => false. It defaults
-
# to 19.
-
# * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
-
# defiance of RFC 4627 to be parsed by the Parser. This option defaults
-
# to false.
-
# * *symbolize_names*: If set to true, returns symbols for the names
-
# (keys) in a JSON object. Otherwise strings are returned. Strings are
-
# the default.
-
# * *create_additions*: If set to false, the Parser doesn't create
-
# additions even if a matching class and create_id was found. This option
-
# defaults to true.
-
# * *object_class*: Defaults to Hash
-
# * *array_class*: Defaults to Array
-
1
def parse(source, opts = {})
-
13
Parser.new(source, opts).parse
-
end
-
-
# Parse the JSON document _source_ into a Ruby data structure and return it.
-
# The bang version of the parse method defaults to the more dangerous values
-
# for the _opts_ hash, so be sure only to parse trusted _source_ documents.
-
#
-
# _opts_ can have the following keys:
-
# * *max_nesting*: The maximum depth of nesting allowed in the parsed data
-
# structures. Enable depth checking with :max_nesting => anInteger. The parse!
-
# methods defaults to not doing max depth checking: This can be dangerous
-
# if someone wants to fill up your stack.
-
# * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in
-
# defiance of RFC 4627 to be parsed by the Parser. This option defaults
-
# to true.
-
# * *create_additions*: If set to false, the Parser doesn't create
-
# additions even if a matching class and create_id was found. This option
-
# defaults to true.
-
1
def parse!(source, opts = {})
-
opts = {
-
:max_nesting => false,
-
:allow_nan => true
-
}.update(opts)
-
Parser.new(source, opts).parse
-
end
-
-
# Generate a JSON document from the Ruby data structure _obj_ and return
-
# it. _state_ is * a JSON::State object,
-
# * or a Hash like object (responding to to_hash),
-
# * an object convertible into a hash by a to_h method,
-
# that is used as or to configure a State object.
-
#
-
# It defaults to a state object, that creates the shortest possible JSON text
-
# in one line, checks for circular data structures and doesn't allow NaN,
-
# Infinity, and -Infinity.
-
#
-
# A _state_ hash can have the following keys:
-
# * *indent*: a string used to indent levels (default: ''),
-
# * *space*: a string that is put after, a : or , delimiter (default: ''),
-
# * *space_before*: a string that is put before a : pair delimiter (default: ''),
-
# * *object_nl*: a string that is put at the end of a JSON object (default: ''),
-
# * *array_nl*: a string that is put at the end of a JSON array (default: ''),
-
# * *allow_nan*: true if NaN, Infinity, and -Infinity should be
-
# generated, otherwise an exception is thrown if these values are
-
# encountered. This options defaults to false.
-
# * *max_nesting*: The maximum depth of nesting allowed in the data
-
# structures from which JSON is to be generated. Disable depth checking
-
# with :max_nesting => false, it defaults to 19.
-
#
-
# See also the fast_generate for the fastest creation method with the least
-
# amount of sanity checks, and the pretty_generate method for some
-
# defaults for pretty output.
-
1
def generate(obj, opts = nil)
-
7
if State === opts
-
state, opts = opts, nil
-
else
-
7
state = SAFE_STATE_PROTOTYPE.dup
-
end
-
7
if opts
-
7
if opts.respond_to? :to_hash
-
7
opts = opts.to_hash
-
elsif opts.respond_to? :to_h
-
opts = opts.to_h
-
else
-
raise TypeError, "can't convert #{opts.class} into Hash"
-
end
-
7
state = state.configure(opts)
-
end
-
7
state.generate(obj)
-
end
-
-
# :stopdoc:
-
# I want to deprecate these later, so I'll first be silent about them, and
-
# later delete them.
-
1
alias unparse generate
-
1
module_function :unparse
-
# :startdoc:
-
-
# Generate a JSON document from the Ruby data structure _obj_ and return it.
-
# This method disables the checks for circles in Ruby objects.
-
#
-
# *WARNING*: Be careful not to pass any Ruby data structures with circles as
-
# _obj_ argument because this will cause JSON to go into an infinite loop.
-
1
def fast_generate(obj, opts = nil)
-
if State === opts
-
state, opts = opts, nil
-
else
-
state = FAST_STATE_PROTOTYPE.dup
-
end
-
if opts
-
if opts.respond_to? :to_hash
-
opts = opts.to_hash
-
elsif opts.respond_to? :to_h
-
opts = opts.to_h
-
else
-
raise TypeError, "can't convert #{opts.class} into Hash"
-
end
-
state.configure(opts)
-
end
-
state.generate(obj)
-
end
-
-
# :stopdoc:
-
# I want to deprecate these later, so I'll first be silent about them, and later delete them.
-
1
alias fast_unparse fast_generate
-
1
module_function :fast_unparse
-
# :startdoc:
-
-
# Generate a JSON document from the Ruby data structure _obj_ and return it.
-
# The returned document is a prettier form of the document returned by
-
# #unparse.
-
#
-
# The _opts_ argument can be used to configure the generator. See the
-
# generate method for a more detailed explanation.
-
1
def pretty_generate(obj, opts = nil)
-
if State === opts
-
state, opts = opts, nil
-
else
-
state = PRETTY_STATE_PROTOTYPE.dup
-
end
-
if opts
-
if opts.respond_to? :to_hash
-
opts = opts.to_hash
-
elsif opts.respond_to? :to_h
-
opts = opts.to_h
-
else
-
raise TypeError, "can't convert #{opts.class} into Hash"
-
end
-
state.configure(opts)
-
end
-
state.generate(obj)
-
end
-
-
# :stopdoc:
-
# I want to deprecate these later, so I'll first be silent about them, and later delete them.
-
1
alias pretty_unparse pretty_generate
-
1
module_function :pretty_unparse
-
# :startdoc:
-
-
1
class << self
-
# The global default options for the JSON.load method:
-
# :max_nesting: false
-
# :allow_nan: true
-
# :quirks_mode: true
-
1
attr_accessor :load_default_options
-
end
-
1
self.load_default_options = {
-
:max_nesting => false,
-
:allow_nan => true,
-
:quirks_mode => true,
-
}
-
-
# Load a ruby data structure from a JSON _source_ and return it. A source can
-
# either be a string-like object, an IO-like object, or an object responding
-
# to the read method. If _proc_ was given, it will be called with any nested
-
# Ruby object as an argument recursively in depth first order. The default
-
# options for the parser can be changed via the load_default_options method.
-
#
-
# This method is part of the implementation of the load/dump interface of
-
# Marshal and YAML.
-
1
def load(source, proc = nil)
-
5
opts = load_default_options
-
5
if source.respond_to? :to_str
-
5
source = source.to_str
-
elsif source.respond_to? :to_io
-
source = source.to_io.read
-
elsif source.respond_to?(:read)
-
source = source.read
-
end
-
5
if opts[:quirks_mode] && (source.nil? || source.empty?)
-
source = 'null'
-
end
-
5
result = parse(source, opts)
-
5
recurse_proc(result, &proc) if proc
-
5
result
-
end
-
-
# Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_
-
1
def recurse_proc(result, &proc)
-
case result
-
when Array
-
result.each { |x| recurse_proc x, &proc }
-
proc.call result
-
when Hash
-
result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
-
proc.call result
-
else
-
proc.call result
-
end
-
end
-
-
1
alias restore load
-
1
module_function :restore
-
-
1
class << self
-
# The global default options for the JSON.dump method:
-
# :max_nesting: false
-
# :allow_nan: true
-
# :quirks_mode: true
-
1
attr_accessor :dump_default_options
-
end
-
1
self.dump_default_options = {
-
:max_nesting => false,
-
:allow_nan => true,
-
:quirks_mode => true,
-
}
-
-
# Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns
-
# the result.
-
#
-
# If anIO (an IO-like object or an object that responds to the write method)
-
# was given, the resulting JSON is written to it.
-
#
-
# If the number of nested arrays or objects exceeds _limit_, an ArgumentError
-
# exception is raised. This argument is similar (but not exactly the
-
# same!) to the _limit_ argument in Marshal.dump.
-
#
-
# The default options for the generator can be changed via the
-
# dump_default_options method.
-
#
-
# This method is part of the implementation of the load/dump interface of
-
# Marshal and YAML.
-
1
def dump(obj, anIO = nil, limit = nil)
-
7
if anIO and limit.nil?
-
anIO = anIO.to_io if anIO.respond_to?(:to_io)
-
unless anIO.respond_to?(:write)
-
limit = anIO
-
anIO = nil
-
end
-
end
-
7
opts = JSON.dump_default_options
-
7
limit and opts.update(:max_nesting => limit)
-
7
result = generate(obj, opts)
-
7
if anIO
-
anIO.write result
-
anIO
-
else
-
7
result
-
end
-
rescue JSON::NestingError
-
raise ArgumentError, "exceed depth limit"
-
end
-
-
# Swap consecutive bytes of _string_ in place.
-
1
def self.swap!(string) # :nodoc:
-
0.upto(string.size / 2) do |i|
-
break unless string[2 * i + 1]
-
string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i]
-
end
-
string
-
end
-
-
# Shortuct for iconv.
-
if ::String.method_defined?(:encode) &&
-
# XXX Rubinius doesn't support ruby 1.9 encoding yet
-
1
defined?(RUBY_ENGINE) && RUBY_ENGINE != 'rbx'
-
then
-
# Encodes string using Ruby's _String.encode_
-
1
def self.iconv(to, from, string)
-
string.encode(to, from)
-
end
-
else
-
require 'iconv'
-
# Encodes string using _iconv_ library
-
def self.iconv(to, from, string)
-
Iconv.conv(to, from, string)
-
end
-
end
-
-
1
if ::Object.method(:const_defined?).arity == 1
-
def self.const_defined_in?(modul, constant)
-
modul.const_defined?(constant)
-
end
-
else
-
1
def self.const_defined_in?(modul, constant)
-
11
modul.const_defined?(constant, false)
-
end
-
end
-
end
-
-
1
module ::Kernel
-
1
private
-
-
# Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
-
# one line.
-
1
def j(*objs)
-
objs.each do |obj|
-
puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
-
end
-
nil
-
end
-
-
# Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with
-
# indentation and over many lines.
-
1
def jj(*objs)
-
objs.each do |obj|
-
puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
-
end
-
nil
-
end
-
-
# If _object_ is string-like, parse the string and return the parsed result as
-
# a Ruby data structure. Otherwise, generate a JSON text from the Ruby data
-
# structure object and return it.
-
#
-
# The _opts_ argument is passed through to generate/parse respectively. See
-
# generate and parse for their documentation.
-
1
def JSON(object, *args)
-
if object.respond_to? :to_str
-
JSON.parse(object.to_str, args.first)
-
else
-
JSON.generate(object, args.first)
-
end
-
end
-
end
-
-
# Extends any Class to include _json_creatable?_ method.
-
1
class ::Class
-
# Returns true if this class can be used to create an instance
-
# from a serialised JSON string. The class has to implement a class
-
# method _json_create_ that expects a hash as first parameter. The hash
-
# should include the required data.
-
1
def json_creatable?
-
respond_to?(:json_create)
-
end
-
end
-
1
if ENV['SIMPLECOV_COVERAGE'].to_i == 1
-
require 'simplecov'
-
SimpleCov.start do
-
add_filter "/tests/"
-
end
-
end
-
1
require 'json/common'
-
-
1
module JSON
-
# This module holds all the modules/classes that implement JSON's
-
# functionality as C extensions.
-
1
module Ext
-
1
require 'json/ext/parser'
-
1
require 'json/ext/generator'
-
1
$DEBUG and warn "Using Ext extension for JSON."
-
1
JSON.parser = Parser
-
1
JSON.generator = Generator
-
end
-
-
1
JSON_LOADED = true unless defined?(::JSON::JSON_LOADED)
-
end
-
1
require 'ostruct'
-
-
1
module JSON
-
1
class GenericObject < OpenStruct
-
1
class << self
-
1
alias [] new
-
-
1
def json_create(data)
-
data = data.dup
-
data.delete JSON.create_id
-
self[data]
-
end
-
end
-
-
1
def to_hash
-
table
-
end
-
-
1
def [](name)
-
table[name.to_sym]
-
end
-
-
1
def []=(name, value)
-
__send__ "#{name}=", value
-
end
-
-
1
def |(other)
-
self.class[other.to_hash.merge(to_hash)]
-
end
-
-
1
def as_json(*)
-
{ JSON.create_id => self.class.name }.merge to_hash
-
end
-
-
1
def to_json(*a)
-
as_json.to_json(*a)
-
end
-
end
-
end
-
1
module JSON
-
# JSON version
-
1
VERSION = '1.7.5'
-
4
VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
-
1
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
-
1
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
-
1
VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
-
end
-
# encoding: utf-8
-
1
module Mail # :doc:
-
-
1
require 'date'
-
1
require 'shellwords'
-
-
1
require 'uri'
-
1
require 'net/smtp'
-
1
require 'mime/types'
-
-
1
if RUBY_VERSION <= '1.8.6'
-
begin
-
require 'tlsmail'
-
rescue LoadError
-
raise "You need to install tlsmail if you are using ruby <= 1.8.6"
-
end
-
end
-
-
1
if RUBY_VERSION >= "1.9.0"
-
1
require 'mail/version_specific/ruby_1_9'
-
1
RubyVer = Ruby19
-
else
-
require 'mail/version_specific/ruby_1_8'
-
RubyVer = Ruby18
-
end
-
-
1
require 'mail/version'
-
-
1
require 'mail/core_extensions/nil'
-
1
require 'mail/core_extensions/object'
-
1
require 'mail/core_extensions/string'
-
1
require 'mail/core_extensions/shell_escape'
-
1
require 'mail/core_extensions/smtp' if RUBY_VERSION < '1.9.3'
-
1
require 'mail/indifferent_hash'
-
-
# Only load our multibyte extensions if AS is not already loaded
-
1
if defined?(ActiveSupport)
-
1
require 'active_support/inflector'
-
else
-
require 'mail/core_extensions/string/access'
-
require 'mail/core_extensions/string/multibyte'
-
require 'mail/multibyte'
-
end
-
-
1
require 'mail/patterns'
-
1
require 'mail/utilities'
-
1
require 'mail/configuration'
-
-
# Autoload mail send and receive classes.
-
1
require 'mail/network'
-
-
1
require 'mail/message'
-
1
require 'mail/part'
-
1
require 'mail/header'
-
1
require 'mail/parts_list'
-
1
require 'mail/attachments_list'
-
1
require 'mail/body'
-
1
require 'mail/field'
-
1
require 'mail/field_list'
-
-
1
require 'mail/envelope'
-
-
1
parsers = %w[ rfc2822_obsolete rfc2822 address_lists phrase_lists
-
date_time received message_ids envelope_from rfc2045
-
mime_version content_type content_disposition
-
content_transfer_encoding content_location ]
-
-
1
parsers.each do |parser|
-
14
begin
-
# Try requiring the pre-compiled ruby version first
-
14
require 'treetop/runtime'
-
14
require "mail/parsers/#{parser}"
-
rescue LoadError
-
# Otherwise, get treetop to compile and load it
-
require 'treetop/runtime'
-
require 'treetop/compiler'
-
Treetop.load(File.join(File.dirname(__FILE__)) + "/mail/parsers/#{parser}")
-
end
-
end
-
-
# Autoload header field elements and transfer encodings.
-
1
require 'mail/elements'
-
1
require 'mail/encodings'
-
1
require 'mail/encodings/base64'
-
1
require 'mail/encodings/quoted_printable'
-
-
1
require 'mail/matchers/has_sent_mail'
-
-
# Finally... require all the Mail.methods
-
1
require 'mail/mail'
-
end
-
# encoding: utf-8
-
1
module Mail
-
-
# = Body
-
#
-
# The body is where the text of the email is stored. Mail treats the body
-
# as a single object. The body itself has no information about boundaries
-
# used in the MIME standard, it just looks at it's content as either a single
-
# block of text, or (if it is a multipart message) as an array of blocks o text.
-
#
-
# A body has to be told to split itself up into a multipart message by calling
-
# #split with the correct boundary. This is because the body object has no way
-
# of knowing what the correct boundary is for itself (there could be many
-
# boundaries in a body in the case of a nested MIME text).
-
#
-
# Once split is called, Mail::Body will slice itself up on this boundary,
-
# assigning anything that appears before the first part to the preamble, and
-
# anything that appears after the closing boundary to the epilogue, then
-
# each part gets initialized into a Mail::Part object.
-
#
-
# The boundary that is used to split up the Body is also stored in the Body
-
# object for use on encoding itself back out to a string. You can
-
# overwrite this if it needs to be changed.
-
#
-
# On encoding, the body will return the preamble, then each part joined by
-
# the boundary, followed by a closing boundary string and then the epilogue.
-
1
class Body
-
-
1
def initialize(string = '')
-
7
@boundary = nil
-
7
@preamble = nil
-
7
@epilogue = nil
-
7
@charset = nil
-
7
@part_sort_order = [ "text/plain", "text/enriched", "text/html" ]
-
7
@parts = Mail::PartsList.new
-
7
if string.blank?
-
4
@raw_source = ''
-
else
-
# Do join first incase we have been given an Array in Ruby 1.9
-
3
if string.respond_to?(:join)
-
@raw_source = string.join('')
-
elsif string.respond_to?(:to_s)
-
3
@raw_source = string.to_s
-
else
-
raise "You can only assign a string or an object that responds_to? :join or :to_s to a body."
-
end
-
end
-
7
@encoding = (only_us_ascii? ? '7bit' : '8bit')
-
7
set_charset
-
end
-
-
# Matches this body with another body. Also matches the decoded value of this
-
# body with a string.
-
#
-
# Examples:
-
#
-
# body = Mail::Body.new('The body')
-
# body == body #=> true
-
#
-
# body = Mail::Body.new('The body')
-
# body == 'The body' #=> true
-
#
-
# body = Mail::Body.new("VGhlIGJvZHk=\n")
-
# body.encoding = 'base64'
-
# body == "The body" #=> true
-
1
def ==(other)
-
if other.class == String
-
self.decoded == other
-
else
-
super
-
end
-
end
-
-
# Accepts a string and performs a regular expression against the decoded text
-
#
-
# Examples:
-
#
-
# body = Mail::Body.new('The body')
-
# body =~ /The/ #=> 0
-
#
-
# body = Mail::Body.new("VGhlIGJvZHk=\n")
-
# body.encoding = 'base64'
-
# body =~ /The/ #=> 0
-
1
def =~(regexp)
-
self.decoded =~ regexp
-
end
-
-
# Accepts a string and performs a regular expression against the decoded text
-
#
-
# Examples:
-
#
-
# body = Mail::Body.new('The body')
-
# body.match(/The/) #=> #<MatchData "The">
-
#
-
# body = Mail::Body.new("VGhlIGJvZHk=\n")
-
# body.encoding = 'base64'
-
# body.match(/The/) #=> #<MatchData "The">
-
1
def match(regexp)
-
self.decoded.match(regexp)
-
end
-
-
# Accepts anything that responds to #to_s and checks if it's a substring of the decoded text
-
#
-
# Examples:
-
#
-
# body = Mail::Body.new('The body')
-
# body.include?('The') #=> true
-
#
-
# body = Mail::Body.new("VGhlIGJvZHk=\n")
-
# body.encoding = 'base64'
-
# body.include?('The') #=> true
-
1
def include?(other)
-
self.decoded.include?(other.to_s)
-
end
-
-
# Allows you to set the sort order of the parts, overriding the default sort order.
-
# Defaults to 'text/plain', then 'text/enriched', then 'text/html' with any other content
-
# type coming after.
-
1
def set_sort_order(order)
-
5
@part_sort_order = order
-
end
-
-
# Allows you to sort the parts according to the default sort order, or the sort order you
-
# set with :set_sort_order.
-
#
-
# sort_parts! is also called from :encode, so there is no need for you to call this explicitly
-
1
def sort_parts!
-
6
@parts.each do |p|
-
4
p.body.set_sort_order(@part_sort_order)
-
4
@parts.sort!(@part_sort_order)
-
4
p.body.sort_parts!
-
end
-
end
-
-
# Returns the raw source that the body was initialized with, without
-
# any tampering
-
1
def raw_source
-
28
@raw_source
-
end
-
-
1
def get_best_encoding(target)
-
8
target_encoding = Mail::Encodings.get_encoding(target)
-
8
target_encoding.get_best_compatible(encoding, raw_source)
-
end
-
-
# Returns a body encoded using transfer_encoding. Multipart always uses an
-
# identiy encoding (i.e. no encoding).
-
# Calling this directly is not a good idea, but supported for compatibility
-
# TODO: Validate that preamble and epilogue are valid for requested encoding
-
1
def encoded(transfer_encoding = '8bit')
-
4
if multipart?
-
1
self.sort_parts!
-
3
encoded_parts = parts.map { |p| p.encoded }
-
1
([preamble] + encoded_parts).join(crlf_boundary) + end_boundary + epilogue.to_s
-
else
-
3
be = get_best_encoding(transfer_encoding)
-
3
dec = Mail::Encodings::get_encoding(encoding)
-
3
enc = Mail::Encodings::get_encoding(be)
-
3
if transfer_encoding == encoding and dec.nil?
-
# Cannot decode, so skip normalization
-
raw_source
-
else
-
# Decode then encode to normalize and allow transforming
-
# from base64 to Q-P and vice versa
-
3
decoded = dec.decode(raw_source)
-
3
if defined?(Encoding) && charset && charset != "US-ASCII"
-
decoded.encode!(charset)
-
decoded.force_encoding('BINARY') unless Encoding.find(charset).ascii_compatible?
-
end
-
3
enc.encode(decoded)
-
end
-
end
-
end
-
-
1
def decoded
-
3
if !Encodings.defined?(encoding)
-
raise UnknownEncodingType, "Don't know how to decode #{encoding}, please call #encoded and decode it yourself."
-
else
-
3
Encodings.get_encoding(encoding).decode(raw_source)
-
end
-
end
-
-
1
def to_s
-
2
decoded
-
end
-
-
1
def charset
-
6
@charset
-
end
-
-
1
def charset=( val )
-
@charset = val
-
end
-
-
1
def encoding(val = nil)
-
20
if val
-
self.encoding = val
-
else
-
20
@encoding
-
end
-
end
-
-
1
def encoding=( val )
-
@encoding = if val == "text" || val.blank?
-
(only_us_ascii? ? '7bit' : '8bit')
-
else
-
val
-
end
-
end
-
-
# Returns the preamble (any text that is before the first MIME boundary)
-
1
def preamble
-
1
@preamble
-
end
-
-
# Sets the preamble to a string (adds text before the first MIME boundary)
-
1
def preamble=( val )
-
@preamble = val
-
end
-
-
# Returns the epilogue (any text that is after the last MIME boundary)
-
1
def epilogue
-
1
@epilogue
-
end
-
-
# Sets the epilogue to a string (adds text after the last MIME boundary)
-
1
def epilogue=( val )
-
@epilogue = val
-
end
-
-
# Returns true if there are parts defined in the body
-
1
def multipart?
-
21
true unless parts.empty?
-
end
-
-
# Returns the boundary used by the body
-
1
def boundary
-
4
@boundary
-
end
-
-
# Allows you to change the boundary of this Body object
-
1
def boundary=( val )
-
1
@boundary = val
-
end
-
-
1
def parts
-
42
@parts
-
end
-
-
1
def <<( val )
-
2
if @parts
-
2
@parts << val
-
else
-
@parts = Mail::PartsList.new[val]
-
end
-
end
-
-
1
def split!(boundary)
-
self.boundary = boundary
-
parts = raw_source.split("--#{boundary}")
-
# Make the preamble equal to the preamble (if any)
-
self.preamble = parts[0].to_s.strip
-
# Make the epilogue equal to the epilogue (if any)
-
self.epilogue = parts[-1].to_s.sub('--', '').strip
-
parts[1...-1].to_a.each { |part| @parts << Mail::Part.new(part) }
-
self
-
end
-
-
1
def only_us_ascii?
-
14
!(raw_source =~ /[^\x01-\x7f]/)
-
end
-
-
1
def empty?
-
!!raw_source.to_s.empty?
-
end
-
-
1
private
-
-
1
def crlf_boundary
-
1
"\r\n\r\n--#{boundary}\r\n"
-
end
-
-
1
def end_boundary
-
1
"\r\n\r\n--#{boundary}--\r\n"
-
end
-
-
1
def set_charset
-
7
only_us_ascii? ? @charset = 'US-ASCII' : @charset = nil
-
end
-
end
-
end
-
# encoding: utf-8
-
#
-
# Thanks to Nicolas Fouché for this wrapper
-
#
-
1
require 'singleton'
-
-
1
module Mail
-
-
# The Configuration class is a Singleton used to hold the default
-
# configuration for all Mail objects.
-
#
-
# Each new mail object gets a copy of these values at initialization
-
# which can be overwritten on a per mail object basis.
-
1
class Configuration
-
1
include Singleton
-
-
1
def initialize
-
1
@delivery_method = nil
-
1
@retriever_method = nil
-
1
super
-
end
-
-
1
def delivery_method(method = nil, settings = {})
-
4
return @delivery_method if @delivery_method && method.nil?
-
1
@delivery_method = lookup_delivery_method(method).new(settings)
-
end
-
-
1
def lookup_delivery_method(method)
-
3
case method
-
when nil
-
1
Mail::SMTP
-
when :smtp
-
Mail::SMTP
-
when :sendmail
-
Mail::Sendmail
-
when :exim
-
Mail::Exim
-
when :file
-
Mail::FileDelivery
-
when :smtp_connection
-
Mail::SMTPConnection
-
when :test
-
Mail::TestMailer
-
else
-
2
method
-
end
-
end
-
-
1
def retriever_method(method = nil, settings = {})
-
return @retriever_method if @retriever_method && method.nil?
-
@retriever_method = lookup_retriever_method(method).new(settings)
-
end
-
-
1
def lookup_retriever_method(method)
-
case method
-
when nil
-
Mail::POP3
-
when :pop3
-
Mail::POP3
-
when :imap
-
Mail::IMAP
-
when :test
-
Mail::TestRetriever
-
else
-
method
-
end
-
end
-
-
1
def param_encode_language(value = nil)
-
value ? @encode_language = value : @encode_language ||= 'en'
-
end
-
-
end
-
-
end
-
# encoding: utf-8
-
-
# This is not loaded if ActiveSupport is already loaded
-
-
1
class NilClass #:nodoc:
-
1
def blank?
-
41630
true
-
end
-
-
1
def to_crlf
-
4
''
-
end
-
-
1
def to_lf
-
''
-
end
-
end
-
# encoding: utf-8
-
-
# This is not loaded if ActiveSupport is already loaded
-
-
1
class Object
-
1
def blank?
-
3294
if respond_to?(:empty?)
-
2130
empty?
-
else
-
1164
!self
-
end
-
end
-
end
-
# encoding: utf-8
-
-
# The following is an adaptation of ruby 1.9.2's shellwords.rb file,
-
# it is modified to include '+' in the allowed list to allow for
-
# sendmail to accept email addresses as the sender with a + in them
-
#
-
1
module Mail
-
1
module ShellEscape
-
# Escapes a string so that it can be safely used in a Bourne shell
-
# command line.
-
#
-
# Note that a resulted string should be used unquoted and is not
-
# intended for use in double quotes nor in single quotes.
-
#
-
# open("| grep #{Shellwords.escape(pattern)} file") { |pipe|
-
# # ...
-
# }
-
#
-
# +String#shellescape+ is a shorthand for this function.
-
#
-
# open("| grep #{pattern.shellescape} file") { |pipe|
-
# # ...
-
# }
-
#
-
1
def escape_for_shell(str)
-
# An empty argument will be skipped, so return empty quotes.
-
return "''" if str.empty?
-
-
str = str.dup
-
-
# Process as a single byte sequence because not all shell
-
# implementations are multibyte aware.
-
str.gsub!(/([^A-Za-z0-9_\s\+\-.,:\/@\n])/n, "\\\\\\1")
-
-
# A LF cannot be escaped with a backslash because a backslash + LF
-
# combo is regarded as line continuation and simply ignored.
-
str.gsub!(/\n/, "'\n'")
-
-
return str
-
end
-
-
1
module_function :escape_for_shell
-
end
-
end
-
-
1
class String
-
# call-seq:
-
# str.shellescape => string
-
#
-
# Escapes +str+ so that it can be safely used in a Bourne shell
-
# command line. See +Shellwords::shellescape+ for details.
-
#
-
1
def escape_for_shell
-
Mail::ShellEscape.escape_for_shell(self)
-
end
-
end
-
# encoding: utf-8
-
1
class String #:nodoc:
-
1
def to_crlf
-
7
to_str.gsub(/\n|\r\n|\r/) { "\r\n" }
-
end
-
-
1
def to_lf
-
6
to_str.gsub(/\n|\r\n|\r/) { "\n" }
-
end
-
-
154
unless String.instance_methods(false).map {|m| m.to_sym}.include?(:blank?)
-
def blank?
-
self !~ /\S/
-
end
-
end
-
-
1
unless method_defined?(:ascii_only?)
-
# Provides all strings with the Ruby 1.9 method of .ascii_only? and
-
# returns true or false
-
US_ASCII_REGEXP = %Q{\x00-\x7f}
-
def ascii_only?
-
!(self =~ /[^#{US_ASCII_REGEXP}]/)
-
end
-
end
-
-
1
def not_ascii_only?
-
2
!ascii_only?
-
end
-
-
1
unless method_defined?(:bytesize)
-
alias :bytesize :length
-
end
-
end
-
1
module Mail
-
1
autoload :Address, 'mail/elements/address'
-
1
autoload :AddressList, 'mail/elements/address_list'
-
1
autoload :ContentDispositionElement, 'mail/elements/content_disposition_element'
-
1
autoload :ContentLocationElement, 'mail/elements/content_location_element'
-
1
autoload :ContentTransferEncodingElement, 'mail/elements/content_transfer_encoding_element'
-
1
autoload :ContentTypeElement, 'mail/elements/content_type_element'
-
1
autoload :DateTimeElement, 'mail/elements/date_time_element'
-
1
autoload :EnvelopeFromElement, 'mail/elements/envelope_from_element'
-
1
autoload :MessageIdsElement, 'mail/elements/message_ids_element'
-
1
autoload :MimeVersionElement, 'mail/elements/mime_version_element'
-
1
autoload :PhraseList, 'mail/elements/phrase_list'
-
1
autoload :ReceivedElement, 'mail/elements/received_element'
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
class Address
-
-
1
include Mail::Utilities
-
-
# Mail::Address handles all email addresses in Mail. It takes an email address string
-
# and parses it, breaking it down into it's component parts and allowing you to get the
-
# address, comments, display name, name, local part, domain part and fully formatted
-
# address.
-
#
-
# Mail::Address requires a correctly formatted email address per RFC2822 or RFC822. It
-
# handles all obsolete versions including obsolete domain routing on the local part.
-
#
-
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
-
# a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
-
# a.address #=> 'mikel@test.lindsaar.net'
-
# a.display_name #=> 'Mikel Lindsaar'
-
# a.local #=> 'mikel'
-
# a.domain #=> 'test.lindsaar.net'
-
# a.comments #=> ['My email address']
-
# a.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
-
1
def initialize(value = nil)
-
4
@output_type = nil
-
4
@tree = nil
-
4
@raw_text = value
-
case
-
when value.nil?
-
@parsed = false
-
return
-
else
-
4
parse(value)
-
4
end
-
end
-
-
# Returns the raw imput of the passed in string, this is before it is passed
-
# by the parser.
-
1
def raw
-
@raw_text
-
end
-
-
# Returns a correctly formatted address for the email going out. If given
-
# an incorrectly formatted address as input, Mail::Address will do it's best
-
# to format it correctly. This includes quoting display names as needed and
-
# putting the address in angle brackets etc.
-
#
-
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
-
# a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
-
1
def format
-
8
parse unless @parsed
-
case
-
when tree.nil?
-
''
-
when display_name
-
4
[quote_phrase(display_name), "<#{address}>", format_comments].compact.join(" ")
-
else
-
4
[address, format_comments].compact.join(" ")
-
8
end
-
end
-
-
# Returns the address that is in the address itself. That is, the
-
# local@domain string, without any angle brackets or the like.
-
#
-
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
-
# a.address #=> 'mikel@test.lindsaar.net'
-
1
def address
-
12
parse unless @parsed
-
12
domain ? "#{local}@#{domain}" : local
-
end
-
-
# Provides a way to assign an address to an already made Mail::Address object.
-
#
-
# a = Address.new
-
# a.address = 'Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>'
-
# a.address #=> 'mikel@test.lindsaar.net'
-
1
def address=(value)
-
parse(value)
-
end
-
-
# Returns the display name of the email address passed in.
-
#
-
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
-
# a.display_name #=> 'Mikel Lindsaar'
-
1
def display_name
-
12
parse unless @parsed
-
12
@display_name ||= get_display_name
-
12
Encodings.decode_encode(@display_name.to_s, @output_type) if @display_name
-
end
-
-
# Provides a way to assign a display name to an already made Mail::Address object.
-
#
-
# a = Address.new
-
# a.address = 'mikel@test.lindsaar.net'
-
# a.display_name = 'Mikel Lindsaar'
-
# a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>'
-
1
def display_name=( str )
-
@display_name = str
-
end
-
-
# Returns the local part (the left hand side of the @ sign in the email address) of
-
# the address
-
#
-
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
-
# a.local #=> 'mikel'
-
1
def local
-
12
parse unless @parsed
-
12
"#{obs_domain_list}#{get_local.strip}" if get_local
-
end
-
-
# Returns the domain part (the right hand side of the @ sign in the email address) of
-
# the address
-
#
-
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
-
# a.domain #=> 'test.lindsaar.net'
-
1
def domain
-
24
parse unless @parsed
-
24
strip_all_comments(get_domain) if get_domain
-
end
-
-
# Returns an array of comments that are in the email, or an empty array if there
-
# are no comments
-
#
-
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
-
# a.comments #=> ['My email address']
-
1
def comments
-
38
parse unless @parsed
-
38
if get_comments.empty?
-
nil
-
else
-
get_comments.map { |c| c.squeeze(" ") }
-
end
-
end
-
-
# Sometimes an address will not have a display name, but might have the name
-
# as a comment field after the address. This returns that name if it exists.
-
#
-
# a = Address.new('mikel@test.lindsaar.net (Mikel Lindsaar)')
-
# a.name #=> 'Mikel Lindsaar'
-
1
def name
-
parse unless @parsed
-
get_name
-
end
-
-
# Returns the format of the address, or returns nothing
-
#
-
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
-
# a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
-
1
def to_s
-
parse unless @parsed
-
format
-
end
-
-
# Shows the Address object basic details, including the Address
-
# a = Address.new('Mikel (My email) <mikel@test.lindsaar.net>')
-
# a.inspect #=> "#<Mail::Address:14184910 Address: |Mikel <mikel@test.lindsaar.net> (My email)| >"
-
1
def inspect
-
parse unless @parsed
-
"#<#{self.class}:#{self.object_id} Address: |#{to_s}| >"
-
end
-
-
1
def encoded
-
8
@output_type = :encode
-
8
format
-
end
-
-
1
def decoded
-
@output_type = :decode
-
format
-
end
-
-
1
private
-
-
1
def parse(value = nil)
-
4
@parsed = true
-
case
-
when value.nil?
-
nil
-
when value.class == String
-
self.tree = Mail::AddressList.new(value).address_nodes.first
-
else
-
4
self.tree = value
-
4
end
-
end
-
-
-
1
def get_domain
-
48
if tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:domain)
-
24
@domain_text ||= tree.angle_addr.addr_spec.domain.text_value.strip
-
24
elsif tree.respond_to?(:domain)
-
24
@domain_text ||= tree.domain.text_value.strip
-
elsif tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:domain)
-
tree.addr_spec.domain.text_value.strip
-
else
-
nil
-
end
-
end
-
-
1
def strip_all_comments(string)
-
26
unless comments.blank?
-
comments.each do |comment|
-
string = string.gsub("(#{comment})", '')
-
end
-
end
-
26
string.strip
-
end
-
-
1
def strip_domain_comments(value)
-
unless comments.blank?
-
comments.each do |comment|
-
if get_domain && get_domain.include?("(#{comment})")
-
value = value.gsub("(#{comment})", '')
-
end
-
end
-
end
-
value.to_s.strip
-
end
-
-
1
def get_comments
-
38
if tree.respond_to?(:comments)
-
38
@comments = tree.comments.map { |c| unparen(c.text_value.to_str) }
-
else
-
@comments = []
-
end
-
end
-
-
1
def get_display_name
-
6
if tree.respond_to?(:display_name)
-
2
name = unquote(tree.display_name.text_value.strip)
-
2
str = strip_all_comments(name.to_s)
-
elsif comments
-
if domain
-
str = strip_domain_comments(format_comments)
-
else
-
str = nil
-
end
-
else
-
4
nil
-
end
-
-
6
if str.blank?
-
nil
-
else
-
2
str
-
end
-
end
-
-
1
def get_name
-
if display_name
-
str = display_name
-
else
-
if comments
-
comment_text = comments.join(' ').squeeze(" ")
-
str = "(#{comment_text})"
-
end
-
end
-
-
if str.blank?
-
nil
-
else
-
unparen(str)
-
end
-
end
-
-
# Provides access to the Treetop parse tree for this address
-
1
def tree
-
366
@tree
-
end
-
-
1
def tree=(value)
-
4
@tree = value
-
end
-
-
1
def format_comments
-
8
if comments
-
comment_text = comments.map {|c| escape_paren(c) }.join(' ').squeeze(" ")
-
@format_comments ||= "(#{comment_text})"
-
else
-
nil
-
end
-
end
-
-
1
def obs_domain_list
-
12
if tree.respond_to?(:angle_addr)
-
36
obs = tree.angle_addr.elements.select { |e| e.respond_to?(:obs_domain_list) }
-
6
!obs.empty? ? obs.first.text_value : nil
-
else
-
nil
-
end
-
end
-
-
1
def get_local
-
case
-
when tree.respond_to?(:local_dot_atom_text)
-
tree.local_dot_atom_text.text_value
-
when tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:local_part)
-
12
tree.angle_addr.addr_spec.local_part.text_value
-
when tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:local_part)
-
tree.addr_spec.local_part.text_value
-
else
-
12
tree && tree.respond_to?(:local_part) ? tree.local_part.text_value : nil
-
24
end
-
end
-
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
class AddressList # :nodoc:
-
-
# Mail::AddressList is the class that parses To, From and other address fields from
-
# emails passed into Mail.
-
#
-
# AddressList provides a way to query the groups and mailbox lists of the passed in
-
# string.
-
#
-
# It can supply all addresses in an array, or return each address as an address object.
-
#
-
# Mail::AddressList requires a correctly formatted group or mailbox list per RFC2822 or
-
# RFC822. It also handles all obsolete versions in those RFCs.
-
#
-
# list = 'ada@test.lindsaar.net, My Group: mikel@test.lindsaar.net, Bob <bob@test.lindsaar.net>;'
-
# a = AddressList.new(list)
-
# a.addresses #=> [#<Mail::Address:14943130 Address: |ada@test.lindsaar.net...
-
# a.group_names #=> ["My Group"]
-
1
def initialize(string)
-
4
if string.blank?
-
@address_nodes = []
-
return self
-
end
-
4
parser = Mail::AddressListsParser.new
-
4
if tree = parser.parse(string)
-
4
@address_nodes = tree.addresses
-
else
-
raise Mail::Field::ParseError.new(AddressListsParser, string, parser.failure_reason)
-
end
-
end
-
-
# Returns a list of address objects from the parsed line
-
1
def addresses
-
@addresses ||= get_addresses.map do |address_tree|
-
4
Mail::Address.new(address_tree)
-
8
end
-
end
-
-
# Returns a list of all recipient syntax trees that are not part of a group
-
1
def individual_recipients # :nodoc:
-
4
@individual_recipients ||= @address_nodes - group_recipients
-
end
-
-
# Returns a list of all recipient syntax trees that are part of a group
-
1
def group_recipients # :nodoc:
-
20
@group_recipients ||= @address_nodes.select { |an| an.respond_to?(:group_name) }
-
end
-
-
# Returns the names as an array of strings of all groups
-
1
def group_names # :nodoc:
-
group_recipients.map { |g| g.group_name.text_value }
-
end
-
-
# Returns a list of address syntax trees
-
1
def address_nodes # :nodoc:
-
@address_nodes
-
end
-
-
1
private
-
-
1
def get_addresses
-
4
(individual_recipients + group_recipients.map { |g| get_group_addresses(g) }).flatten
-
end
-
-
1
def get_group_addresses(g)
-
if g.group_list.respond_to?(:addresses)
-
g.group_list.addresses
-
else
-
[]
-
end
-
end
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
class ContentTransferEncodingElement
-
-
1
include Mail::Utilities
-
-
1
def initialize( string )
-
6
parser = Mail::ContentTransferEncodingParser.new
-
case
-
when string.blank?
-
@encoding = ''
-
when tree = parser.parse(string.to_s.downcase)
-
6
@encoding = tree.encoding.text_value
-
else
-
raise Mail::Field::ParseError.new(ContentTransferEncodingElement, string, parser.failure_reason)
-
6
end
-
end
-
-
1
def encoding
-
8
@encoding
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
class ContentTypeElement # :nodoc:
-
-
1
include Mail::Utilities
-
-
1
def initialize( string )
-
6
parser = Mail::ContentTypeParser.new
-
6
if tree = parser.parse(cleaned(string))
-
6
@main_type = tree.main_type.text_value.downcase
-
6
@sub_type = tree.sub_type.text_value.downcase
-
6
@parameters = tree.parameters
-
else
-
raise Mail::Field::ParseError.new(ContentTypeElement, string, parser.failure_reason)
-
end
-
end
-
-
1
def main_type
-
5
@main_type
-
end
-
-
1
def sub_type
-
4
@sub_type
-
end
-
-
1
def parameters
-
5
@parameters
-
end
-
-
1
def cleaned(string)
-
6
string =~ /(.+);\s*$/ ? $1 : string
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
class MessageIdsElement
-
-
1
include Mail::Utilities
-
-
1
def initialize(string)
-
4
parser = Mail::MessageIdsParser.new
-
4
if tree = parser.parse(string)
-
8
@message_ids = tree.message_ids.map { |msg_id| clean_msg_id(msg_id.text_value) }
-
else
-
raise Mail::Field::ParseError.new(MessageIdsElement, string, parser.failure_reason)
-
end
-
end
-
-
1
def message_ids
-
@message_ids
-
end
-
-
1
def message_id
-
6
@message_ids.first
-
end
-
-
1
def clean_msg_id( val )
-
4
val =~ /.*<(.*)>.*/ ; $1
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
class MimeVersionElement
-
-
1
include Mail::Utilities
-
-
1
def initialize( string )
-
4
parser = Mail::MimeVersionParser.new
-
4
if tree = parser.parse(string)
-
4
@major = tree.major.text_value
-
4
@minor = tree.minor.text_value
-
else
-
raise Mail::Field::ParseError.new(MimeVersionElement, string, parser.failure_reason)
-
end
-
end
-
-
1
def major
-
4
@major
-
end
-
-
1
def minor
-
4
@minor
-
end
-
-
end
-
end
-
# encoding: utf-8
-
-
1
module Mail
-
# Raised when attempting to decode an unknown encoding type
-
1
class UnknownEncodingType < StandardError #:nodoc:
-
end
-
-
1
module Encodings
-
-
1
include Mail::Patterns
-
1
extend Mail::Utilities
-
-
1
@transfer_encodings = {}
-
-
# Register transfer encoding
-
#
-
# Example
-
#
-
# Encodings.register "base64", Mail::Encodings::Base64
-
1
def Encodings.register(name, cls)
-
5
@transfer_encodings[get_name(name)] = cls
-
end
-
-
# Is the encoding we want defined?
-
#
-
# Example:
-
#
-
# Encodings.defined?(:base64) #=> true
-
1
def Encodings.defined?( str )
-
11
@transfer_encodings.include? get_name(str)
-
end
-
-
# Gets a defined encoding type, QuotedPrintable or Base64 for now.
-
#
-
# Each encoding needs to be defined as a Mail::Encodings::ClassName for
-
# this to work, allows us to add other encodings in the future.
-
#
-
# Example:
-
#
-
# Encodings.get_encoding(:base64) #=> Mail::Encodings::Base64
-
1
def Encodings.get_encoding( str )
-
31
@transfer_encodings[get_name(str)]
-
end
-
-
1
def Encodings.get_all
-
@transfer_encodings.values
-
end
-
-
1
def Encodings.get_name(enc)
-
55
enc = enc.to_s.gsub("-", "_").downcase
-
end
-
-
# Encodes a parameter value using URI Escaping, note the language field 'en' can
-
# be set using Mail::Configuration, like so:
-
#
-
# Mail.defaults.do
-
# param_encode_language 'jp'
-
# end
-
#
-
# The character set used for encoding will either be the value of $KCODE for
-
# Ruby < 1.9 or the encoding on the string passed in.
-
#
-
# Example:
-
#
-
# Mail::Encodings.param_encode("This is fun") #=> "us-ascii'en'This%20is%20fun"
-
1
def Encodings.param_encode(str)
-
case
-
when str.ascii_only? && str =~ TOKEN_UNSAFE
-
1
%Q{"#{str}"}
-
when str.ascii_only?
-
1
str
-
else
-
RubyVer.param_encode(str)
-
2
end
-
end
-
-
# Decodes a parameter value using URI Escaping.
-
#
-
# Example:
-
#
-
# Mail::Encodings.param_decode("This%20is%20fun", 'us-ascii') #=> "This is fun"
-
#
-
# str = Mail::Encodings.param_decode("This%20is%20fun", 'iso-8559-1')
-
# str.encoding #=> 'ISO-8859-1' ## Only on Ruby 1.9
-
# str #=> "This is fun"
-
1
def Encodings.param_decode(str, encoding)
-
RubyVer.param_decode(str, encoding)
-
end
-
-
# Decodes or encodes a string as needed for either Base64 or QP encoding types in
-
# the =?<encoding>?[QB]?<string>?=" format.
-
#
-
# The output type needs to be :decode to decode the input string or :encode to
-
# encode the input string. The character set used for encoding will either be
-
# the value of $KCODE for Ruby < 1.9 or the encoding on the string passed in.
-
#
-
# On encoding, will only send out Base64 encoded strings.
-
1
def Encodings.decode_encode(str, output_type)
-
case
-
when output_type == :decode
-
4
Encodings.value_decode(str)
-
else
-
8
if str.ascii_only?
-
8
str
-
else
-
Encodings.b_value_encode(str, find_encoding(str))
-
end
-
12
end
-
end
-
-
# Decodes a given string as Base64 or Quoted Printable, depending on what
-
# type it is.
-
#
-
# String has to be of the format =?<encoding>?[QB]?<string>?=
-
1
def Encodings.value_decode(str)
-
# Optimization: If there's no encoded-words in the string, just return it
-
4
return str unless str.index("=?")
-
-
str = str.gsub(/\?=(\s*)=\?/, '?==?') # Remove whitespaces between 'encoded-word's
-
-
# Split on white-space boundaries with capture, so we capture the white-space as well
-
str.split(/([ \t])/).map do |text|
-
if text.index('=?') .nil?
-
text
-
else
-
# Join QP encoded-words that are adjacent to avoid decoding partial chars
-
text.gsub!(/\?\=\=\?.+?\?[Qq]\?/m, '') if text =~ /\?==\?/
-
-
# Search for occurences of quoted strings or plain strings
-
text.scan(/( # Group around entire regex to include it in matches
-
\=\?[^?]+\?([QB])\?[^?]+?\?\= # Quoted String with subgroup for encoding method
-
| # or
-
.+?(?=\=\?|$) # Plain String
-
)/xmi).map do |matches|
-
string, method = *matches
-
if method == 'b' || method == 'B'
-
b_value_decode(string)
-
elsif method == 'q' || method == 'Q'
-
q_value_decode(string)
-
else
-
string
-
end
-
end
-
end
-
end.join("")
-
end
-
-
# Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
-
1
def Encodings.unquote_and_convert_to(str, to_encoding)
-
original_encoding = split_encoding_from_string( str )
-
-
output = value_decode( str ).to_s
-
-
if original_encoding.to_s.downcase.gsub("-", "") == to_encoding.to_s.downcase.gsub("-", "")
-
output
-
elsif original_encoding && to_encoding
-
begin
-
if RUBY_VERSION >= '1.9'
-
output.encode(to_encoding)
-
else
-
require 'iconv'
-
Iconv.iconv(to_encoding, original_encoding, output).first
-
end
-
rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
-
# the 'from' parameter specifies a charset other than what the text
-
# actually is...not much we can do in this case but just return the
-
# unconverted text.
-
#
-
# Ditto if either parameter represents an unknown charset, like
-
# X-UNKNOWN.
-
output
-
end
-
else
-
output
-
end
-
end
-
-
1
def Encodings.address_encode(address, charset = 'utf-8')
-
4
if address.is_a?(Array)
-
# loop back through for each element
-
address.map { |a| Encodings.address_encode(a, charset) }.join(", ")
-
else
-
# find any word boundary that is not ascii and encode it
-
4
encode_non_usascii(address, charset)
-
end
-
end
-
-
1
def Encodings.encode_non_usascii(address, charset)
-
4
return address if address.ascii_only? or charset.nil?
-
us_ascii = %Q{\x00-\x7f}
-
# Encode any non usascii strings embedded inside of quotes
-
address.gsub!(/(".*?[^#{us_ascii}].*?")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
-
# Then loop through all remaining items and encode as needed
-
tokens = address.split(/\s/)
-
map_with_index(tokens) do |word, i|
-
if word.ascii_only?
-
word
-
else
-
previous_non_ascii = tokens[i-1] && !tokens[i-1].ascii_only?
-
if previous_non_ascii
-
word = " #{word}"
-
end
-
Encodings.b_value_encode(word, charset)
-
end
-
end.join(' ')
-
end
-
-
# Encode a string with Base64 Encoding and returns it ready to be inserted
-
# as a value for a field, that is, in the =?<charset>?B?<string>?= format
-
#
-
# Example:
-
#
-
# Encodings.b_value_encode('This is あ string', 'UTF-8')
-
# #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
-
1
def Encodings.b_value_encode(encoded_str, encoding = nil)
-
return encoded_str if encoded_str.to_s.ascii_only?
-
string, encoding = RubyVer.b_value_encode(encoded_str, encoding)
-
map_lines(string) do |str|
-
"=?#{encoding}?B?#{str.chomp}?="
-
end.join(" ")
-
end
-
-
# Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
-
# as a value for a field, that is, in the =?<charset>?Q?<string>?= format
-
#
-
# Example:
-
#
-
# Encodings.q_value_encode('This is あ string', 'UTF-8')
-
# #=> "=?UTF-8?Q?This_is_=E3=81=82_string?="
-
1
def Encodings.q_value_encode(encoded_str, encoding = nil)
-
return encoded_str if encoded_str.to_s.ascii_only?
-
string, encoding = RubyVer.q_value_encode(encoded_str, encoding)
-
string.gsub!("=\r\n", '') # We already have limited the string to the length we want
-
map_lines(string) do |str|
-
"=?#{encoding}?Q?#{str.chomp.gsub(/ /, '_')}?="
-
end.join(" ")
-
end
-
-
1
private
-
-
# Decodes a Base64 string from the "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=" format
-
#
-
# Example:
-
#
-
# Encodings.b_value_decode("=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=")
-
# #=> 'This is あ string'
-
1
def Encodings.b_value_decode(str)
-
RubyVer.b_value_decode(str)
-
end
-
-
# Decodes a Quoted-Printable string from the "=?UTF-8?Q?This_is_=E3=81=82_string?=" format
-
#
-
# Example:
-
#
-
# Encodings.q_value_decode("=?UTF-8?Q?This_is_=E3=81=82_string?=")
-
# #=> 'This is あ string'
-
1
def Encodings.q_value_decode(str)
-
RubyVer.q_value_decode(str)
-
end
-
-
1
def Encodings.split_encoding_from_string( str )
-
match = str.match(/\=\?([^?]+)?\?[QB]\?(.+)?\?\=/mi)
-
if match
-
match[1]
-
else
-
nil
-
end
-
end
-
-
1
def Encodings.find_encoding(str)
-
RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
-
end
-
end
-
end
-
# encoding: utf-8
-
1
require 'mail/encodings/8bit'
-
-
1
module Mail
-
1
module Encodings
-
1
class SevenBit < EightBit
-
1
NAME = '7bit'
-
-
1
PRIORITY = 1
-
-
# 7bit and 8bit operate the same
-
-
# Decode the string
-
1
def self.decode(str)
-
6
super
-
end
-
-
# Encode the string
-
1
def self.encode(str)
-
3
super
-
end
-
-
# Idenity encodings have a fixed cost, 1 byte out per 1 byte in
-
1
def self.cost(str)
-
super
-
end
-
-
1
Encodings.register(NAME, self)
-
end
-
end
-
end
-
# encoding: utf-8
-
1
require 'mail/encodings/binary'
-
-
1
module Mail
-
1
module Encodings
-
1
class EightBit < Binary
-
1
NAME = '8bit'
-
-
1
PRIORITY = 4
-
-
# 8bit is an identiy encoding, meaning nothing to do
-
-
# Decode the string
-
1
def self.decode(str)
-
6
str.to_lf
-
end
-
-
# Encode the string
-
1
def self.encode(str)
-
3
str.to_crlf
-
end
-
-
# Idenity encodings have a fixed cost, 1 byte out per 1 byte in
-
1
def self.cost(str)
-
1.0
-
end
-
-
1
Encodings.register(NAME, self)
-
end
-
end
-
end
-
# encoding: utf-8
-
1
require 'mail/encodings/7bit'
-
-
1
module Mail
-
1
module Encodings
-
1
class Base64 < SevenBit
-
1
NAME = 'base64'
-
-
1
PRIORITY = 3
-
-
1
def self.can_encode?(enc)
-
true
-
end
-
-
# Decode the string from Base64
-
1
def self.decode(str)
-
RubyVer.decode_base64( str )
-
end
-
-
# Encode the string to Base64
-
1
def self.encode(str)
-
RubyVer.encode_base64( str ).to_crlf
-
end
-
-
# Base64 has a fixed cost, 4 bytes out per 3 bytes in
-
1
def self.cost(str)
-
4.0/3
-
end
-
-
1
Encodings.register(NAME, self)
-
end
-
end
-
end
-
# encoding: utf-8
-
1
require 'mail/encodings/transfer_encoding'
-
-
1
module Mail
-
1
module Encodings
-
1
class Binary < TransferEncoding
-
1
NAME = 'binary'
-
-
1
PRIORITY = 5
-
-
# Binary is an identiy encoding, meaning nothing to do
-
-
# Decode the string
-
1
def self.decode(str)
-
str
-
end
-
-
# Encode the string
-
1
def self.encode(str)
-
str
-
end
-
-
# Idenity encodings have a fixed cost, 1 byte out per 1 byte in
-
1
def self.cost(str)
-
1.0
-
end
-
-
1
Encodings.register(NAME, self)
-
end
-
end
-
end
-
# encoding: utf-8
-
1
require 'mail/encodings/7bit'
-
-
1
module Mail
-
1
module Encodings
-
1
class QuotedPrintable < SevenBit
-
1
NAME='quoted-printable'
-
-
1
PRIORITY = 2
-
-
1
def self.can_encode?(str)
-
EightBit.can_encode? str
-
end
-
-
# Decode the string from Quoted-Printable
-
1
def self.decode(str)
-
str.unpack("M*").first
-
end
-
-
1
def self.encode(str)
-
[str].pack("M").gsub(/\n/, "\r\n")
-
end
-
-
1
def self.cost(str)
-
# These bytes probably do not need encoding
-
c = str.count("\x9\xA\xD\x20-\x3C\x3E-\x7E")
-
# Everything else turns into =XX where XX is a
-
# two digit hex number (taking 3 bytes)
-
total = (str.bytesize - c)*3 + c
-
total.to_f/str.bytesize
-
end
-
-
1
private
-
-
1
Encodings.register(NAME, self)
-
end
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
module Encodings
-
1
class TransferEncoding
-
1
NAME = ''
-
-
1
PRIORITY = -1
-
-
1
def self.can_transport?(enc)
-
8
enc = Encodings.get_name(enc)
-
8
if Encodings.defined? enc
-
8
Encodings.get_encoding(enc).new.is_a? self
-
else
-
false
-
end
-
end
-
-
1
def self.can_encode?(enc)
-
can_transport? enc
-
end
-
-
1
def self.cost(str)
-
raise "Unimplemented"
-
end
-
-
1
def self.to_s
-
10
self::NAME
-
end
-
-
1
def self.get_best_compatible(source_encoding, str)
-
8
if self.can_transport? source_encoding then
-
8
source_encoding
-
else
-
choices = []
-
Encodings.get_all.each do |enc|
-
choices << enc if self.can_transport? enc and enc.can_encode? source_encoding
-
end
-
best = nil
-
best_cost = 100
-
choices.each do |enc|
-
this_cost = enc.cost str
-
if this_cost < best_cost then
-
best_cost = this_cost
-
best = enc
-
elsif this_cost == best_cost then
-
best = enc if enc::PRIORITY < best::PRIORITY
-
end
-
end
-
best
-
end
-
end
-
-
1
def to_s
-
self.class.to_s
-
end
-
end
-
end
-
end
-
# encoding: utf-8
-
#
-
# = Mail Envelope
-
#
-
# The Envelope class provides a field for the first line in an
-
# mbox file, that looks like "From mikel@test.lindsaar.net DATETIME"
-
#
-
# This envelope class reads that line, and turns it into an
-
# Envelope.from and Envelope.date for your use.
-
1
module Mail
-
1
class Envelope < StructuredField
-
-
1
def initialize(*args)
-
super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
-
end
-
-
1
def tree
-
@element ||= Mail::EnvelopeFromElement.new(value)
-
@tree ||= @element.tree
-
end
-
-
1
def element
-
@element ||= Mail::EnvelopeFromElement.new(value)
-
end
-
-
1
def date
-
::DateTime.parse("#{element.date_time}")
-
end
-
-
1
def from
-
element.address
-
end
-
-
end
-
end
-
1
require 'mail/fields'
-
-
# encoding: utf-8
-
1
module Mail
-
# Provides a single class to call to create a new structured or unstructured
-
# field. Works out per RFC what field of field it is being given and returns
-
# the correct field of class back on new.
-
#
-
# ===Per RFC 2822
-
#
-
# 2.2. Header Fields
-
#
-
# Header fields are lines composed of a field name, followed by a colon
-
# (":"), followed by a field body, and terminated by CRLF. A field
-
# name MUST be composed of printable US-ASCII characters (i.e.,
-
# characters that have values between 33 and 126, inclusive), except
-
# colon. A field body may be composed of any US-ASCII characters,
-
# except for CR and LF. However, a field body may contain CRLF when
-
# used in header "folding" and "unfolding" as described in section
-
# 2.2.3. All field bodies MUST conform to the syntax described in
-
# sections 3 and 4 of this standard.
-
#
-
1
class Field
-
-
1
include Patterns
-
1
include Comparable
-
-
1
STRUCTURED_FIELDS = %w[ bcc cc content-description content-disposition
-
content-id content-location content-transfer-encoding
-
content-type date from in-reply-to keywords message-id
-
mime-version received references reply-to
-
resent-bcc resent-cc resent-date resent-from
-
resent-message-id resent-sender resent-to
-
return-path sender to ]
-
-
1
KNOWN_FIELDS = STRUCTURED_FIELDS + ['comments', 'subject']
-
-
# Generic Field Exception
-
1
class FieldError < StandardError
-
end
-
-
# Raised when a parsing error has occurred (ie, a StructuredField has tried
-
# to parse a field that is invalid or improperly written)
-
1
class ParseError < FieldError #:nodoc:
-
1
attr_accessor :element, :value, :reason
-
-
1
def initialize(element, value, reason)
-
@element = element
-
@value = value
-
@reason = reason
-
super("#{element} can not parse |#{value}|\nReason was: #{reason}")
-
end
-
end
-
-
# Raised when attempting to set a structured field's contents to an invalid syntax
-
1
class SyntaxError < FieldError #:nodoc:
-
end
-
-
# Accepts a string:
-
#
-
# Field.new("field-name: field data")
-
#
-
# Or name, value pair:
-
#
-
# Field.new("field-name", "value")
-
#
-
# Or a name by itself:
-
#
-
# Field.new("field-name")
-
#
-
# Note, does not want a terminating carriage return. Returns
-
# self appropriately parsed. If value is not a string, then
-
# it will be passed through as is, for example, content-type
-
# field can accept an array with the type and a hash of
-
# parameters:
-
#
-
# Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
-
1
def initialize(name, value = nil, charset = 'utf-8')
-
case
-
when name =~ /:/ # Field.new("field-name: field data")
-
charset = value unless value.blank?
-
name, value = split(name)
-
create_field(name, value, charset)
-
when name !~ /:/ && value.blank? # Field.new("field-name")
-
10
create_field(name, nil, charset)
-
else # Field.new("field-name", "value")
-
16
create_field(name, value, charset)
-
26
end
-
26
return self
-
end
-
-
1
def field=(value)
-
30
@field = value
-
end
-
-
1
def field
-
1291
@field
-
end
-
-
1
def name
-
168
field.name
-
end
-
-
1
def value
-
field.value
-
end
-
-
1
def value=(val)
-
create_field(name, val, charset)
-
end
-
-
1
def to_s
-
3
field.to_s
-
end
-
-
1
def update(name, value)
-
4
create_field(name, value, charset)
-
end
-
-
1
def same( other )
-
76
match_to_s(other.name, field.name)
-
end
-
-
1
alias_method :==, :same
-
-
1
def <=>( other )
-
46
self_order = FIELD_ORDER.rindex(self.name.to_s.downcase) || 100
-
46
other_order = FIELD_ORDER.rindex(other.name.to_s.downcase) || 100
-
46
self_order <=> other_order
-
end
-
-
1
def method_missing(name, *args, &block)
-
1044
field.send(name, *args, &block)
-
end
-
-
1
FIELD_ORDER = %w[ return-path received
-
resent-date resent-from resent-sender resent-to
-
resent-cc resent-bcc resent-message-id
-
date from sender reply-to to cc bcc
-
message-id in-reply-to references
-
subject comments keywords
-
mime-version content-type content-transfer-encoding
-
content-location content-disposition content-description ]
-
-
1
private
-
-
1
def split(raw_field)
-
match_data = raw_field.mb_chars.match(/^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})?$/)
-
[match_data[1].to_s.mb_chars.strip, match_data[2].to_s.mb_chars.strip]
-
rescue
-
STDERR.puts "WARNING: Could not parse (and so ignorning) '#{raw_field}'"
-
end
-
-
1
def create_field(name, value, charset)
-
30
begin
-
30
self.field = new_field(name, value, charset)
-
rescue Mail::Field::ParseError => e
-
self.field = Mail::UnstructuredField.new(name, value)
-
self.field.errors << [name, value, e]
-
self.field
-
end
-
end
-
-
1
def new_field(name, value, charset)
-
# Could do this with constantize and make it "as DRY as", but a simple case
-
# statement is, well, simpler...
-
30
case name.to_s.downcase
-
when /^to$/i
-
2
ToField.new(value, charset)
-
when /^cc$/i
-
CcField.new(value, charset)
-
when /^bcc$/i
-
BccField.new(value, charset)
-
when /^message-id$/i
-
2
MessageIdField.new(value, charset)
-
when /^in-reply-to$/i
-
InReplyToField.new(value, charset)
-
when /^references$/i
-
ReferencesField.new(value, charset)
-
when /^subject$/i
-
2
SubjectField.new(value, charset)
-
when /^comments$/i
-
CommentsField.new(value, charset)
-
when /^keywords$/i
-
KeywordsField.new(value, charset)
-
when /^date$/i
-
4
DateField.new(value, charset)
-
when /^from$/i
-
2
FromField.new(value, charset)
-
when /^sender$/i
-
SenderField.new(value, charset)
-
when /^reply-to$/i
-
ReplyToField.new(value, charset)
-
when /^resent-date$/i
-
ResentDateField.new(value, charset)
-
when /^resent-from$/i
-
ResentFromField.new(value, charset)
-
when /^resent-sender$/i
-
ResentSenderField.new(value, charset)
-
when /^resent-to$/i
-
ResentToField.new(value, charset)
-
when /^resent-cc$/i
-
ResentCcField.new(value, charset)
-
when /^resent-bcc$/i
-
ResentBccField.new(value, charset)
-
when /^resent-message-id$/i
-
ResentMessageIdField.new(value, charset)
-
when /^return-path$/i
-
ReturnPathField.new(value, charset)
-
when /^received$/i
-
ReceivedField.new(value, charset)
-
when /^mime-version$/i
-
4
MimeVersionField.new(value, charset)
-
when /^content-transfer-encoding$/i
-
6
ContentTransferEncodingField.new(value, charset)
-
when /^content-description$/i
-
ContentDescriptionField.new(value, charset)
-
when /^content-disposition$/i
-
ContentDispositionField.new(value, charset)
-
when /^content-type$/i
-
6
ContentTypeField.new(value, charset)
-
when /^content-id$/i
-
2
ContentIdField.new(value, charset)
-
when /^content-location$/i
-
ContentLocationField.new(value, charset)
-
else
-
OptionalField.new(name, value, charset)
-
end
-
-
end
-
-
end
-
-
end
-
# encoding: utf-8
-
1
module Mail
-
-
# Field List class provides an enhanced array that keeps a list of
-
# email fields in order. And allows you to insert new fields without
-
# having to worry about the order they will appear in.
-
1
class FieldList < Array
-
-
1
include Enumerable
-
-
1
def <<( new_field )
-
26
current_entry = self.rindex(new_field)
-
26
if current_entry
-
self.insert((current_entry + 1), new_field)
-
else
-
26
insert_idx = -1
-
26
self.each_with_index do |item, idx|
-
46
case item <=> new_field
-
when -1
-
32
next
-
when 0
-
next
-
when 1
-
14
insert_idx = idx
-
14
break
-
end
-
end
-
26
insert(insert_idx, new_field)
-
end
-
end
-
-
end
-
end
-
1
module Mail
-
1
autoload :UnstructuredField, 'mail/fields/unstructured_field'
-
1
autoload :StructuredField, 'mail/fields/structured_field'
-
1
autoload :OptionalField, 'mail/fields/optional_field'
-
-
1
autoload :BccField, 'mail/fields/bcc_field'
-
1
autoload :CcField, 'mail/fields/cc_field'
-
1
autoload :CommentsField, 'mail/fields/comments_field'
-
1
autoload :ContentDescriptionField, 'mail/fields/content_description_field'
-
1
autoload :ContentDispositionField, 'mail/fields/content_disposition_field'
-
1
autoload :ContentIdField, 'mail/fields/content_id_field'
-
1
autoload :ContentLocationField, 'mail/fields/content_location_field'
-
1
autoload :ContentTransferEncodingField, 'mail/fields/content_transfer_encoding_field'
-
1
autoload :ContentTypeField, 'mail/fields/content_type_field'
-
1
autoload :DateField, 'mail/fields/date_field'
-
1
autoload :FromField, 'mail/fields/from_field'
-
1
autoload :InReplyToField, 'mail/fields/in_reply_to_field'
-
1
autoload :KeywordsField, 'mail/fields/keywords_field'
-
1
autoload :MessageIdField, 'mail/fields/message_id_field'
-
1
autoload :MimeVersionField, 'mail/fields/mime_version_field'
-
1
autoload :ReceivedField, 'mail/fields/received_field'
-
1
autoload :ReferencesField, 'mail/fields/references_field'
-
1
autoload :ReplyToField, 'mail/fields/reply_to_field'
-
1
autoload :ResentBccField, 'mail/fields/resent_bcc_field'
-
1
autoload :ResentCcField, 'mail/fields/resent_cc_field'
-
1
autoload :ResentDateField, 'mail/fields/resent_date_field'
-
1
autoload :ResentFromField, 'mail/fields/resent_from_field'
-
1
autoload :ResentMessageIdField, 'mail/fields/resent_message_id_field'
-
1
autoload :ResentSenderField, 'mail/fields/resent_sender_field'
-
1
autoload :ResentToField, 'mail/fields/resent_to_field'
-
1
autoload :ReturnPathField, 'mail/fields/return_path_field'
-
1
autoload :SenderField, 'mail/fields/sender_field'
-
1
autoload :SubjectField, 'mail/fields/subject_field'
-
1
autoload :ToField, 'mail/fields/to_field'
-
end
-
1
module Mail
-
-
1
class AddressContainer < Array
-
-
1
def initialize(field, list = [])
-
4
@field = field
-
4
super(list)
-
end
-
-
1
def << (address)
-
@field << address
-
end
-
-
end
-
-
end
-
# encoding: utf-8
-
1
require 'mail/fields/common/address_container'
-
-
1
module Mail
-
1
module CommonAddress # :nodoc:
-
-
1
def parse(val = value)
-
4
unless val.blank?
-
4
@tree = AddressList.new(encode_if_needed(val))
-
else
-
nil
-
end
-
end
-
-
1
def charset
-
4
@charset
-
end
-
-
1
def encode_if_needed(val)
-
4
Encodings.address_encode(val, charset)
-
end
-
-
# Allows you to iterate through each address object in the syntax tree
-
1
def each
-
tree.addresses.each do |address|
-
yield(address)
-
end
-
end
-
-
# Returns the address string of all the addresses in the address list
-
1
def addresses
-
8
list = tree.addresses.map { |a| a.address }
-
4
Mail::AddressContainer.new(self, list)
-
end
-
-
# Returns the formatted string of all the addresses in the address list
-
1
def formatted
-
list = tree.addresses.map { |a| a.format }
-
Mail::AddressContainer.new(self, list)
-
end
-
-
# Returns the display name of all the addresses in the address list
-
1
def display_names
-
list = tree.addresses.map { |a| a.display_name }
-
Mail::AddressContainer.new(self, list)
-
end
-
-
# Returns the actual address objects in the address list
-
1
def addrs
-
list = tree.addresses
-
Mail::AddressContainer.new(self, list)
-
end
-
-
# Returns a hash of group name => address strings for the address list
-
1
def groups
-
8
@groups = Hash.new
-
8
tree.group_recipients.each do |group|
-
@groups[group.group_name.text_value.to_str] = get_group_addresses(group.group_list)
-
end
-
8
@groups
-
end
-
-
# Returns the addresses that are part of groups
-
1
def group_addresses
-
4
groups.map { |k,v| v.map { |a| a.format } }.flatten
-
end
-
-
# Returns the name of all the groups in a string
-
1
def group_names # :nodoc:
-
tree.group_names
-
end
-
-
1
def default
-
4
addresses
-
end
-
-
1
def <<(val)
-
case
-
when val.nil?
-
raise ArgumentError, "Need to pass an address to <<"
-
when val.blank?
-
parse(encoded)
-
else
-
parse((formatted + [val]).join(", "))
-
end
-
end
-
-
1
private
-
-
1
def do_encode(field_name)
-
4
return '' if value.blank?
-
12
address_array = tree.addresses.reject { |a| group_addresses.include?(a.encoded) }.compact.map { |a| a.encoded }
-
4
address_text = address_array.join(", \r\n\s")
-
4
group_array = groups.map { |k,v| "#{k}: #{v.map { |a| a.encoded }.join(", \r\n\s")};" }
-
4
group_text = group_array.join(" \r\n\s")
-
12
return_array = [address_text, group_text].reject { |a| a.blank? }
-
4
"#{field_name}: #{return_array.join(", \r\n\s")}\r\n"
-
end
-
-
1
def do_decode
-
return nil if value.blank?
-
address_array = tree.addresses.reject { |a| group_addresses.include?(a.decoded) }.map { |a| a.decoded }
-
address_text = address_array.join(", ")
-
group_array = groups.map { |k,v| "#{k}: #{v.map { |a| a.decoded }.join(", ")};" }
-
group_text = group_array.join(" ")
-
return_array = [address_text, group_text].reject { |a| a.blank? }
-
return_array.join(", ")
-
end
-
-
# Returns the syntax tree of the Addresses
-
1
def tree # :nodoc:
-
16
@tree ||= AddressList.new(value)
-
end
-
-
1
def get_group_addresses(group_list)
-
if group_list.respond_to?(:addresses)
-
group_list.addresses.map do |address_tree|
-
Mail::Address.new(address_tree)
-
end
-
else
-
[]
-
end
-
end
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
module CommonDate # :nodoc:
-
# Returns a date time object of the parsed date
-
1
def date_time
-
::DateTime.parse("#{element.date_string} #{element.time_string}")
-
end
-
-
1
def default
-
date_time
-
end
-
-
1
def parse(val = value)
-
unless val.blank?
-
@element = Mail::DateTimeElement.new(val)
-
@tree = @element.tree
-
else
-
nil
-
end
-
end
-
-
1
private
-
-
1
def do_encode(field_name)
-
4
"#{field_name}: #{value}\r\n"
-
end
-
-
1
def do_decode
-
"#{value}"
-
end
-
-
1
def element
-
@element ||= Mail::DateTimeElement.new(value)
-
end
-
-
# Returns the syntax tree of the Date
-
1
def tree
-
@tree ||= element.tree
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
module CommonField # :nodoc:
-
-
1
def name=(value)
-
30
@name = value
-
end
-
-
1
def name
-
983
@name ||= nil
-
end
-
-
1
def value=(value)
-
36
@length = nil
-
36
@tree = nil
-
36
@element = nil
-
36
@value = value
-
end
-
-
1
def value
-
48
@value
-
end
-
-
1
def to_s
-
3
decoded
-
end
-
-
1
def default
-
decoded
-
end
-
-
1
def field_length
-
@length ||= "#{name}: #{encode(decoded)}".length
-
end
-
-
1
def responsible_for?( val )
-
844
name.to_s.downcase == val.to_s.downcase
-
end
-
-
1
private
-
-
1
def strip_field(field_name, value)
-
23
if value.is_a?(Array)
-
value
-
else
-
23
value.to_s.gsub(/#{field_name}:\s+/i, '')
-
end
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
module CommonMessageId # :nodoc:
-
1
def element
-
8
@element ||= Mail::MessageIdsElement.new(value) unless value.blank?
-
end
-
-
1
def parse(val = value)
-
2
unless val.blank?
-
2
@element = Mail::MessageIdsElement.new(val)
-
else
-
nil
-
end
-
end
-
-
1
def message_id
-
4
element.message_id if element
-
end
-
-
1
def message_ids
-
element.message_ids if element
-
end
-
-
1
def default
-
return nil unless message_ids
-
if message_ids.length == 1
-
message_ids[0]
-
else
-
message_ids
-
end
-
end
-
-
1
private
-
-
1
def do_encode(field_name)
-
2
%Q{#{field_name}: #{do_decode}\r\n}
-
end
-
-
1
def do_decode
-
4
"#{message_ids.map { |m| "<#{m}>" }.join(' ')}" if message_ids
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
-
# ParameterHash is an intelligent Hash that allows you to add
-
# parameter values including the MIME extension paramaters that
-
# have the name*0="blah", name*1="bleh" keys, and will just return
-
# a single key called name="blahbleh" and do any required un-encoding
-
# to make that happen
-
# Parameters are defined in RFC2045, split keys are in RFC2231
-
-
1
class ParameterHash < IndifferentHash
-
-
1
include Mail::Utilities
-
-
1
def [](key_name)
-
27
key_pattern = Regexp.escape(key_name.to_s)
-
27
pairs = []
-
27
exact = nil
-
27
each do |k,v|
-
34
if k =~ /^#{key_pattern}(\*|$)/i
-
23
if $1 == '*'
-
pairs << [k, v]
-
else
-
23
exact = k
-
end
-
end
-
end
-
27
if pairs.empty? # Just dealing with a single value pair
-
27
super(exact || key_name)
-
else # Dealing with a multiple value pair or a single encoded value pair
-
string = pairs.sort { |a,b| a.first.to_s <=> b.first.to_s }.map { |v| v.last }.join('')
-
if mt = string.match(/([\w\-]+)'(\w\w)'(.*)/)
-
string = mt[3]
-
encoding = mt[1]
-
else
-
encoding = nil
-
end
-
Mail::Encodings.param_decode(string, encoding)
-
end
-
end
-
-
1
def encoded
-
5
map.sort { |a,b| a.first.to_s <=> b.first.to_s }.map do |key_name, value|
-
5
unless value.ascii_only?
-
value = Mail::Encodings.param_encode(value)
-
key_name = "#{key_name}*"
-
end
-
5
%Q{#{key_name}=#{quote_token(value)}}
-
end.join(";\r\n\s")
-
end
-
-
1
def decoded
-
9
map.sort { |a,b| a.first.to_s <=> b.first.to_s }.map do |key_name, value|
-
9
%Q{#{key_name}=#{quote_token(value)}}
-
end.join("; ")
-
end
-
end
-
end
-
# encoding: utf-8
-
#
-
#
-
#
-
1
module Mail
-
1
class ContentIdField < StructuredField
-
-
1
FIELD_NAME = 'content-id'
-
1
CAPITALIZED_FIELD = "Content-ID"
-
-
1
def initialize(value = nil, charset = 'utf-8')
-
2
self.charset = charset
-
2
@uniq = 1
-
2
if value.blank?
-
2
value = generate_content_id
-
else
-
value = strip_field(FIELD_NAME, value)
-
end
-
2
super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
-
2
self.parse
-
2
self
-
end
-
-
1
def parse(val = value)
-
2
unless val.blank?
-
2
@element = Mail::MessageIdsElement.new(val)
-
end
-
end
-
-
1
def element
-
2
@element ||= Mail::MessageIdsElement.new(value)
-
end
-
-
1
def name
-
66
'Content-ID'
-
end
-
-
1
def content_id
-
2
element.message_id
-
end
-
-
1
def to_s
-
2
"<#{content_id}>"
-
end
-
-
# TODO: Fix this up
-
1
def encoded
-
2
"#{CAPITALIZED_FIELD}: #{to_s}\r\n"
-
end
-
-
1
def decoded
-
"#{to_s}"
-
end
-
-
1
private
-
-
1
def generate_content_id
-
2
fqdn = ::Socket.gethostname
-
2
"<#{Mail.random_tag}@#{fqdn}.mail>"
-
end
-
-
end
-
end
-
# encoding: utf-8
-
#
-
#
-
#
-
1
module Mail
-
1
class ContentTransferEncodingField < StructuredField
-
-
1
FIELD_NAME = 'content-transfer-encoding'
-
1
CAPITALIZED_FIELD = 'Content-Transfer-Encoding'
-
-
1
def initialize(value = nil, charset = 'utf-8')
-
6
self.charset = charset
-
6
value = '7bit' if value.to_s =~ /7-bit/i
-
6
value = '8bit' if value.to_s =~ /8-bit/i
-
6
super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
-
6
self.parse
-
6
self
-
end
-
-
1
def parse(val = value)
-
6
unless val.blank?
-
6
@element = Mail::ContentTransferEncodingElement.new(val)
-
end
-
end
-
-
1
def tree
-
STDERR.puts("tree is deprecated. Please use encoding to get parse result\n#{caller}")
-
@element ||= Mail::ContentTransferEncodingElement.new(value)
-
@tree ||= @element.tree
-
end
-
-
1
def element
-
8
@element ||= Mail::ContentTransferEncodingElement.new(value)
-
end
-
-
1
def encoding
-
8
element.encoding
-
end
-
-
# TODO: Fix this up
-
1
def encoded
-
4
"#{CAPITALIZED_FIELD}: #{encoding}\r\n"
-
end
-
-
1
def decoded
-
4
encoding
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
require 'mail/fields/common/parameter_hash'
-
-
1
module Mail
-
1
class ContentTypeField < StructuredField
-
-
1
FIELD_NAME = 'content-type'
-
1
CAPITALIZED_FIELD = 'Content-Type'
-
-
1
def initialize(value = nil, charset = 'utf-8')
-
6
self.charset = charset
-
6
if value.class == Array
-
1
@main_type = value[0]
-
1
@sub_type = value[1]
-
1
@parameters = ParameterHash.new.merge!(value.last)
-
else
-
5
@main_type = nil
-
5
@sub_type = nil
-
5
@parameters = nil
-
5
value = strip_field(FIELD_NAME, value)
-
end
-
6
super(CAPITALIZED_FIELD, value, charset)
-
6
self.parse
-
6
self
-
end
-
-
1
def parse(val = value)
-
6
unless val.blank?
-
6
self.value = val
-
6
@element = nil
-
6
element
-
end
-
end
-
-
1
def element
-
20
begin
-
20
@element ||= Mail::ContentTypeElement.new(value)
-
rescue
-
attempt_to_clean
-
end
-
end
-
-
1
def attempt_to_clean
-
# Sanitize the value, handle special cases
-
@element ||= Mail::ContentTypeElement.new(sanatize(value))
-
rescue
-
# All else fails, just get the MIME media type
-
@element ||= Mail::ContentTypeElement.new(get_mime_type(value))
-
end
-
-
1
def main_type
-
50
@main_type ||= element.main_type
-
end
-
-
1
def sub_type
-
22
@sub_type ||= element.sub_type
-
end
-
-
1
def string
-
22
"#{main_type}/#{sub_type}"
-
end
-
-
1
def default
-
5
decoded
-
end
-
-
1
alias :content_type :string
-
-
1
def parameters
-
62
unless @parameters
-
5
@parameters = ParameterHash.new
-
5
element.parameters.each { |p| @parameters.merge!(p) }
-
end
-
62
@parameters
-
end
-
-
1
def ContentTypeField.with_boundary(type)
-
new("#{type}; boundary=#{generate_boundary}")
-
end
-
-
1
def ContentTypeField.generate_boundary
-
1
"--==_mimepart_#{Mail.random_tag}"
-
end
-
-
1
def value
-
12
if @value.class == Array
-
1
"#{@main_type}/#{@sub_type}; #{stringify(parameters)}"
-
else
-
11
@value
-
end
-
end
-
-
1
def stringify(params)
-
3
params.map { |k,v| "#{k}=#{Encodings.param_encode(v)}" }.join("; ")
-
end
-
-
1
def filename
-
case
-
when parameters['filename']
-
@filename = parameters['filename']
-
when parameters['name']
-
@filename = parameters['name']
-
else
-
2
@filename = nil
-
2
end
-
2
@filename
-
end
-
-
# TODO: Fix this up
-
1
def encoded
-
4
if parameters.length > 0
-
4
p = ";\r\n\s#{parameters.encoded}"
-
else
-
p = ""
-
end
-
4
"#{CAPITALIZED_FIELD}: #{content_type}#{p}\r\n"
-
end
-
-
1
def decoded
-
8
if parameters.length > 0
-
8
p = "; #{parameters.decoded}"
-
else
-
p = ""
-
end
-
8
"#{content_type}" + p
-
end
-
-
1
private
-
-
1
def method_missing(name, *args, &block)
-
if name.to_s =~ /(\w+)=/
-
self.parameters[$1] = args.first
-
@value = "#{content_type}; #{stringify(parameters)}"
-
else
-
super
-
end
-
end
-
-
# Various special cases from random emails found that I am not going to change
-
# the parser for
-
1
def sanatize( val )
-
-
# TODO: check if there are cases where whitespace is not a separator
-
val = val.tr(' ',';').
-
squeeze(';').
-
gsub(';', '; '). #use '; ' as a separator (or EOL)
-
gsub(/;\s*$/,'') #remove trailing to keep examples below
-
-
if val =~ /(boundary=(\S*))/i
-
val = "#{$`.downcase}boundary=#{$2}#{$'.downcase}"
-
else
-
val.downcase!
-
end
-
-
case
-
when val.chomp =~ /^\s*([\w\-]+)\/([\w\-]+)\s*;;+(.*)$/i
-
# Handles 'text/plain;; format="flowed"' (double semi colon)
-
"#{$1}/#{$2}; #{$3}"
-
when val.chomp =~ /^\s*([\w\-]+)\/([\w\-]+)\s*;\s?(ISO[\w\-]+)$/i
-
# Microsoft helper:
-
# Handles 'type/subtype;ISO-8559-1'
-
"#{$1}/#{$2}; charset=#{quote_atom($3)}"
-
when val.chomp =~ /^text;?$/i
-
# Handles 'text;' and 'text'
-
"text/plain;"
-
when val.chomp =~ /^(\w+);\s(.*)$/i
-
# Handles 'text; <parameters>'
-
"text/plain; #{$2}"
-
when val =~ /([\w\-]+\/[\w\-]+);\scharset="charset="(\w+)""/i
-
# Handles text/html; charset="charset="GB2312""
-
"#{$1}; charset=#{quote_atom($2)}"
-
when val =~ /([\w\-]+\/[\w\-]+);\s+(.*)/i
-
type = $1
-
# Handles misquoted param values
-
# e.g: application/octet-stream; name=archiveshelp1[1].htm
-
# and: audio/x-midi;\r\n\sname=Part .exe
-
params = $2.to_s.split(/\s+/)
-
params = params.map { |i| i.to_s.chomp.strip }
-
params = params.map { |i| i.split(/\s*\=\s*/) }
-
params = params.map { |i| "#{i[0]}=#{dquote(i[1].to_s)}" }.join('; ')
-
"#{type}; #{params}"
-
when val =~ /^\s*$/
-
'text/plain'
-
else
-
''
-
end
-
end
-
-
1
def get_mime_type( val )
-
case
-
when val =~ /^([\w\-]+)\/([\w\-]+);.+$/i
-
"#{$1}/#{$2}"
-
else
-
'text/plain'
-
end
-
end
-
end
-
end
-
# encoding: utf-8
-
#
-
# = Date Field
-
#
-
# The Date field inherits from StructuredField and handles the Date: header
-
# field in the email.
-
#
-
# Sending date to a mail message will instantiate a Mail::Field object that
-
# has a DateField as it's field type. This includes all Mail::CommonAddress
-
# module instance methods.
-
#
-
# There must be excatly one Date field in an RFC2822 email.
-
#
-
# == Examples:
-
#
-
# mail = Mail.new
-
# mail.date = 'Mon, 24 Nov 1997 14:22:01 -0800'
-
# mail.date #=> #<DateTime: 211747170121/86400,-1/3,2299161>
-
# mail.date.to_s #=> 'Mon, 24 Nov 1997 14:22:01 -0800'
-
# mail[:date] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::DateField:0x180e1c4
-
# mail['date'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::DateField:0x180e1c4
-
# mail['Date'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::DateField:0x180e1c4
-
#
-
1
require 'mail/fields/common/common_date'
-
-
1
module Mail
-
1
class DateField < StructuredField
-
-
1
include Mail::CommonDate
-
-
1
FIELD_NAME = 'date'
-
1
CAPITALIZED_FIELD = "Date"
-
-
1
def initialize(value = nil, charset = 'utf-8')
-
4
self.charset = charset
-
4
if value.blank?
-
4
value = ::DateTime.now.strftime('%a, %d %b %Y %H:%M:%S %z')
-
else
-
value = strip_field(FIELD_NAME, value)
-
value.to_s.gsub!(/\(.*?\)/, '')
-
value = ::DateTime.parse(value.to_s.squeeze(" ")).strftime('%a, %d %b %Y %H:%M:%S %z')
-
end
-
4
super(CAPITALIZED_FIELD, value, charset)
-
rescue ArgumentError => e
-
raise e unless "invalid date"==e.message
-
end
-
-
1
def encoded
-
4
do_encode(CAPITALIZED_FIELD)
-
end
-
-
1
def decoded
-
do_decode
-
end
-
-
end
-
end
-
# encoding: utf-8
-
#
-
# = From Field
-
#
-
# The From field inherits from StructuredField and handles the From: header
-
# field in the email.
-
#
-
# Sending from to a mail message will instantiate a Mail::Field object that
-
# has a FromField as it's field type. This includes all Mail::CommonAddress
-
# module instance metods.
-
#
-
# Only one From field can appear in a header, though it can have multiple
-
# addresses and groups of addresses.
-
#
-
# == Examples:
-
#
-
# mail = Mail.new
-
# mail.from = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.from #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
-
# mail[:from] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::FromField:0x180e1c4
-
# mail['from'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::FromField:0x180e1c4
-
# mail['From'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::FromField:0x180e1c4
-
#
-
# mail[:from].encoded #=> 'from: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
-
# mail[:from].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail[:from].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
# mail[:from].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
-
#
-
1
require 'mail/fields/common/common_address'
-
-
1
module Mail
-
1
class FromField < StructuredField
-
-
1
include Mail::CommonAddress
-
-
1
FIELD_NAME = 'from'
-
1
CAPITALIZED_FIELD = 'From'
-
-
1
def initialize(value = nil, charset = 'utf-8')
-
2
self.charset = charset
-
2
super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
-
2
self.parse
-
2
self
-
end
-
-
1
def encoded
-
2
do_encode(CAPITALIZED_FIELD)
-
end
-
-
1
def decoded
-
do_decode
-
end
-
-
end
-
end
-
# encoding: utf-8
-
#
-
# = Message-ID Field
-
#
-
# The Message-ID field inherits from StructuredField and handles the
-
# Message-ID: header field in the email.
-
#
-
# Sending message_id to a mail message will instantiate a Mail::Field object that
-
# has a MessageIdField as it's field type. This includes all Mail::CommonMessageId
-
# module instance metods.
-
#
-
# Only one MessageId field can appear in a header, and syntactically it can only have
-
# one Message ID. The message_ids method call has been left in however as it will only
-
# return the one message id, ie, an array of length 1.
-
#
-
# Note that, the #message_ids method will return an array of message IDs without the
-
# enclosing angle brackets which per RFC are not syntactically part of the message id.
-
#
-
# == Examples:
-
#
-
# mail = Mail.new
-
# mail.message_id = '<F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom>'
-
# mail.message_id #=> '<F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom>'
-
# mail[:message_id] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::MessageIdField:0x180e1c4
-
# mail['message_id'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::MessageIdField:0x180e1c4
-
# mail['Message-ID'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::MessageIdField:0x180e1c4
-
#
-
# mail[:message_id].message_id #=> 'F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom'
-
# mail[:message_id].message_ids #=> ['F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom']
-
#
-
1
require 'mail/fields/common/common_message_id'
-
-
1
module Mail
-
1
class MessageIdField < StructuredField
-
-
1
include Mail::CommonMessageId
-
-
1
FIELD_NAME = 'message-id'
-
1
CAPITALIZED_FIELD = 'Message-ID'
-
-
1
def initialize(value = nil, charset = 'utf-8')
-
2
self.charset = charset
-
2
@uniq = 1
-
2
if value.blank?
-
2
self.name = CAPITALIZED_FIELD
-
2
self.value = generate_message_id
-
else
-
super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
-
end
-
2
self.parse
-
2
self
-
-
end
-
-
1
def name
-
43
'Message-ID'
-
end
-
-
1
def message_ids
-
4
[message_id]
-
end
-
-
1
def to_s
-
"<#{message_id}>"
-
end
-
-
1
def encoded
-
2
do_encode(CAPITALIZED_FIELD)
-
end
-
-
1
def decoded
-
do_decode
-
end
-
-
1
private
-
-
1
def generate_message_id
-
2
fqdn = ::Socket.gethostname
-
2
"<#{Mail.random_tag}@#{fqdn}.mail>"
-
end
-
-
end
-
end
-
# encoding: utf-8
-
#
-
#
-
#
-
1
module Mail
-
1
class MimeVersionField < StructuredField
-
-
1
FIELD_NAME = 'mime-version'
-
1
CAPITALIZED_FIELD = 'Mime-Version'
-
-
1
def initialize(value = nil, charset = 'utf-8')
-
4
self.charset = charset
-
4
if value.blank?
-
2
value = '1.0'
-
end
-
4
super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
-
4
self.parse
-
4
self
-
-
end
-
-
1
def parse(val = value)
-
4
unless val.blank?
-
4
@element = Mail::MimeVersionElement.new(val)
-
end
-
end
-
-
1
def element
-
8
@element ||= Mail::MimeVersionElement.new(value)
-
end
-
-
1
def version
-
4
"#{element.major}.#{element.minor}"
-
end
-
-
1
def major
-
element.major.to_i
-
end
-
-
1
def minor
-
element.minor.to_i
-
end
-
-
1
def encoded
-
4
"#{CAPITALIZED_FIELD}: #{version}\r\n"
-
end
-
-
1
def decoded
-
version
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
require 'mail/fields/common/common_field'
-
-
1
module Mail
-
# Provides access to a structured header field
-
#
-
# ===Per RFC 2822:
-
# 2.2.2. Structured Header Field Bodies
-
#
-
# Some field bodies in this standard have specific syntactical
-
# structure more restrictive than the unstructured field bodies
-
# described above. These are referred to as "structured" field bodies.
-
# Structured field bodies are sequences of specific lexical tokens as
-
# described in sections 3 and 4 of this standard. Many of these tokens
-
# are allowed (according to their syntax) to be introduced or end with
-
# comments (as described in section 3.2.3) as well as the space (SP,
-
# ASCII value 32) and horizontal tab (HTAB, ASCII value 9) characters
-
# (together known as the white space characters, WSP), and those WSP
-
# characters are subject to header "folding" and "unfolding" as
-
# described in section 2.2.3. Semantic analysis of structured field
-
# bodies is given along with their syntax.
-
1
class StructuredField
-
-
1
include Mail::CommonField
-
1
include Mail::Utilities
-
-
1
def initialize(name = nil, value = nil, charset = nil)
-
26
self.name = name
-
26
self.value = value
-
26
self.charset = charset
-
26
self
-
end
-
-
1
def charset
-
4
@charset
-
end
-
-
1
def charset=(val)
-
54
@charset = val
-
end
-
-
1
def default
-
4
decoded
-
end
-
-
1
def errors
-
6
[]
-
end
-
-
end
-
end
-
# encoding: utf-8
-
#
-
# subject = "Subject:" unstructured CRLF
-
1
module Mail
-
1
class SubjectField < UnstructuredField
-
-
1
FIELD_NAME = 'subject'
-
1
CAPITALIZED_FIELD = "Subject"
-
-
1
def initialize(value = nil, charset = 'utf-8')
-
2
self.charset = charset
-
2
super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
-
end
-
-
end
-
end
-
# encoding: utf-8
-
#
-
# = To Field
-
#
-
# The To field inherits to StructuredField and handles the To: header
-
# field in the email.
-
#
-
# Sending to to a mail message will instantiate a Mail::Field object that
-
# has a ToField as it's field type. This includes all Mail::CommonAddress
-
# module instance metods.
-
#
-
# Only one To field can appear in a header, though it can have multiple
-
# addresses and groups of addresses.
-
#
-
# == Examples:
-
#
-
# mail = Mail.new
-
# mail.to = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.to #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
-
# mail[:to] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
-
# mail['to'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
-
# mail['To'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
-
#
-
# mail[:to].encoded #=> 'To: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
-
# mail[:to].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail[:to].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
# mail[:to].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
-
#
-
1
require 'mail/fields/common/common_address'
-
-
1
module Mail
-
1
class ToField < StructuredField
-
-
1
include Mail::CommonAddress
-
-
1
FIELD_NAME = 'to'
-
1
CAPITALIZED_FIELD = 'To'
-
-
1
def initialize(value = nil, charset = 'utf-8')
-
2
self.charset = charset
-
2
super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
-
2
self.parse
-
2
self
-
end
-
-
1
def encoded
-
2
do_encode(CAPITALIZED_FIELD)
-
end
-
-
1
def decoded
-
do_decode
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
require 'mail/fields/common/common_field'
-
-
1
module Mail
-
# Provides access to an unstructured header field
-
#
-
# ===Per RFC 2822:
-
# 2.2.1. Unstructured Header Field Bodies
-
#
-
# Some field bodies in this standard are defined simply as
-
# "unstructured" (which is specified below as any US-ASCII characters,
-
# except for CR and LF) with no further restrictions. These are
-
# referred to as unstructured field bodies. Semantically, unstructured
-
# field bodies are simply to be treated as a single line of characters
-
# with no further processing (except for header "folding" and
-
# "unfolding" as described in section 2.2.3).
-
1
class UnstructuredField
-
-
1
include Mail::CommonField
-
1
include Mail::Utilities
-
-
1
attr_accessor :charset
-
1
attr_reader :errors
-
-
1
def initialize(name, value, charset = nil)
-
2
@errors = []
-
2
if charset
-
2
self.charset = charset
-
else
-
if value.to_s.respond_to?(:encoding)
-
self.charset = value.to_s.encoding
-
else
-
self.charset = $KCODE
-
end
-
end
-
2
self.name = name
-
2
self.value = value
-
2
self
-
end
-
-
1
def encoded
-
2
do_encode
-
end
-
-
1
def decoded
-
4
do_decode
-
end
-
-
1
def default
-
2
decoded
-
end
-
-
1
def parse # An unstructured field does not parse
-
self
-
end
-
-
1
private
-
-
1
def do_encode
-
2
value.nil? ? '' : "#{wrapped_value}\r\n"
-
end
-
-
1
def do_decode
-
4
result = value.blank? ? nil : Encodings.decode_encode(value, :decode)
-
4
result.encode!(value.encoding || "UTF-8") if RUBY_VERSION >= '1.9' && !result.blank?
-
4
result
-
end
-
-
# 2.2.3. Long Header Fields
-
#
-
# Each header field is logically a single line of characters comprising
-
# the field name, the colon, and the field body. For convenience
-
# however, and to deal with the 998/78 character limitations per line,
-
# the field body portion of a header field can be split into a multiple
-
# line representation; this is called "folding". The general rule is
-
# that wherever this standard allows for folding white space (not
-
# simply WSP characters), a CRLF may be inserted before any WSP. For
-
# example, the header field:
-
#
-
# Subject: This is a test
-
#
-
# can be represented as:
-
#
-
# Subject: This
-
# is a test
-
#
-
# Note: Though structured field bodies are defined in such a way that
-
# folding can take place between many of the lexical tokens (and even
-
# within some of the lexical tokens), folding SHOULD be limited to
-
# placing the CRLF at higher-level syntactic breaks. For instance, if
-
# a field body is defined as comma-separated values, it is recommended
-
# that folding occur after the comma separating the structured items in
-
# preference to other places where the field could be folded, even if
-
# it is allowed elsewhere.
-
1
def wrapped_value # :nodoc:
-
2
wrap_lines(name, fold("#{name}: ".length))
-
end
-
-
# 6.2. Display of 'encoded-word's
-
#
-
# When displaying a particular header field that contains multiple
-
# 'encoded-word's, any 'linear-white-space' that separates a pair of
-
# adjacent 'encoded-word's is ignored. (This is to allow the use of
-
# multiple 'encoded-word's to represent long strings of unencoded text,
-
# without having to separate 'encoded-word's where spaces occur in the
-
# unencoded text.)
-
1
def wrap_lines(name, folded_lines)
-
2
result = ["#{name}: #{folded_lines.shift}"]
-
2
result.concat(folded_lines)
-
2
result.join("\r\n\s")
-
end
-
-
1
def fold(prepend = 0) # :nodoc:
-
2
encoding = normalized_encoding
-
2
decoded_string = decoded.to_s
-
2
should_encode = decoded_string.not_ascii_only?
-
2
if should_encode
-
first = true
-
words = decoded_string.split(/[ \t]/).map do |word|
-
if first
-
first = !first
-
else
-
word = " " << word
-
end
-
if word.not_ascii_only?
-
word
-
else
-
word.scan(/.{7}|.+$/)
-
end
-
end.flatten
-
else
-
2
words = decoded_string.split(/[ \t]/)
-
end
-
-
2
folded_lines = []
-
2
while !words.empty?
-
2
limit = 78 - prepend
-
2
limit = limit - 7 - encoding.length if should_encode
-
2
line = ""
-
2
while !words.empty?
-
4
break unless word = words.first.dup
-
4
word.encode!(charset) if defined?(Encoding) && charset
-
4
word = encode(word) if should_encode
-
4
word = encode_crlf(word)
-
# Skip to next line if we're going to go past the limit
-
# Unless this is the first word, in which case we're going to add it anyway
-
# Note: This means that a word that's longer than 998 characters is going to break the spec. Please fix if this is a problem for you.
-
# (The fix, it seems, would be to use encoded-word encoding on it, because that way you can break it across multiple lines and
-
# the linebreak will be ignored)
-
4
break if !line.empty? && (line.length + word.length + 1 > limit)
-
# Remove the word from the queue ...
-
4
words.shift
-
# Add word separator
-
4
line << " " unless (line.empty? || should_encode)
-
# ... add it in encoded form to the current line
-
4
line << word
-
end
-
# Encode the line if necessary
-
2
line = "=?#{encoding}?Q?#{line}?=" if should_encode
-
# Add the line to the output and reset the prepend
-
2
folded_lines << line
-
2
prepend = 0
-
end
-
2
folded_lines
-
end
-
-
1
def encode(value)
-
value = [value].pack("M").gsub("=\n", '')
-
value.gsub!(/"/, '=22')
-
value.gsub!(/\(/, '=28')
-
value.gsub!(/\)/, '=29')
-
value.gsub!(/\?/, '=3F')
-
value.gsub!(/_/, '=5F')
-
value.gsub!(/ /, '_')
-
value
-
end
-
-
1
def encode_crlf(value)
-
4
value.gsub!("\r", '=0D')
-
4
value.gsub!("\n", '=0A')
-
4
value
-
end
-
-
1
def normalized_encoding
-
2
encoding = charset.to_s.upcase.gsub('_', '-')
-
2
encoding = 'UTF-8' if encoding == 'UTF8' # Ruby 1.8.x and $KCODE == 'u'
-
2
encoding
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
-
# Provides access to a header object.
-
#
-
# ===Per RFC2822
-
#
-
# 2.2. Header Fields
-
#
-
# Header fields are lines composed of a field name, followed by a colon
-
# (":"), followed by a field body, and terminated by CRLF. A field
-
# name MUST be composed of printable US-ASCII characters (i.e.,
-
# characters that have values between 33 and 126, inclusive), except
-
# colon. A field body may be composed of any US-ASCII characters,
-
# except for CR and LF. However, a field body may contain CRLF when
-
# used in header "folding" and "unfolding" as described in section
-
# 2.2.3. All field bodies MUST conform to the syntax described in
-
# sections 3 and 4 of this standard.
-
1
class Header
-
1
include Patterns
-
1
include Utilities
-
1
include Enumerable
-
-
# Creates a new header object.
-
#
-
# Accepts raw text or nothing. If given raw text will attempt to parse
-
# it and split it into the various fields, instantiating each field as
-
# it goes.
-
#
-
# If it finds a field that should be a structured field (such as content
-
# type), but it fails to parse it, it will simply make it an unstructured
-
# field and leave it alone. This will mean that the data is preserved but
-
# no automatic processing of that field will happen. If you find one of
-
# these cases, please make a patch and send it in, or at the least, send
-
# me the example so we can fix it.
-
1
def initialize(header_text = nil, charset = nil)
-
4
@errors = []
-
4
@charset = charset
-
4
self.raw_source = header_text.to_crlf
-
4
split_header if header_text
-
end
-
-
# The preserved raw source of the header as you passed it in, untouched
-
# for your Regexing glory.
-
1
def raw_source
-
@raw_source
-
end
-
-
# Returns an array of all the fields in the header in order that they
-
# were read in.
-
1
def fields
-
244
@fields ||= FieldList.new
-
end
-
-
# 3.6. Field definitions
-
#
-
# It is important to note that the header fields are not guaranteed to
-
# be in a particular order. They may appear in any order, and they
-
# have been known to be reordered occasionally when transported over
-
# the Internet. However, for the purposes of this standard, header
-
# fields SHOULD NOT be reordered when a message is transported or
-
# transformed. More importantly, the trace header fields and resent
-
# header fields MUST NOT be reordered, and SHOULD be kept in blocks
-
# prepended to the message. See sections 3.6.6 and 3.6.7 for more
-
# information.
-
#
-
# Populates the fields container with Field objects in the order it
-
# receives them in.
-
#
-
# Acceps an array of field string values, for example:
-
#
-
# h = Header.new
-
# h.fields = ['From: mikel@me.com', 'To: bob@you.com']
-
1
def fields=(unfolded_fields)
-
@fields = Mail::FieldList.new
-
warn "Warning: more than 1000 header fields only using the first 1000" if unfolded_fields.length > 1000
-
unfolded_fields[0..1000].each do |field|
-
-
field = Field.new(field, nil, charset)
-
field.errors.each { |error| self.errors << error }
-
selected = select_field_for(field.name)
-
-
if selected.any? && limited_field?(field.name)
-
selected.first.update(field.name, field.value)
-
else
-
@fields << field
-
end
-
end
-
-
end
-
-
1
def errors
-
@errors
-
end
-
-
# 3.6. Field definitions
-
#
-
# The following table indicates limits on the number of times each
-
# field may occur in a message header as well as any special
-
# limitations on the use of those fields. An asterisk next to a value
-
# in the minimum or maximum column indicates that a special restriction
-
# appears in the Notes column.
-
#
-
# <snip table from 3.6>
-
#
-
# As per RFC, many fields can appear more than once, we will return a string
-
# of the value if there is only one header, or if there is more than one
-
# matching header, will return an array of values in order that they appear
-
# in the header ordered from top to bottom.
-
#
-
# Example:
-
#
-
# h = Header.new
-
# h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
-
# h['To'] #=> 'mikel@me.com'
-
# h['X-Mail-SPAM'] #=> ['15', '20']
-
1
def [](name)
-
162
name = dasherize(name).downcase
-
162
selected = select_field_for(name)
-
case
-
when selected.length > 1
-
selected.map { |f| f }
-
when !selected.blank?
-
119
selected.first
-
else
-
nil
-
162
end
-
end
-
-
# Sets the FIRST matching field in the header to passed value, or deletes
-
# the FIRST field matched from the header if passed nil
-
#
-
# Example:
-
#
-
# h = Header.new
-
# h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
-
# h['To'] = 'bob@you.com'
-
# h['To'] #=> 'bob@you.com'
-
# h['X-Mail-SPAM'] = '10000'
-
# h['X-Mail-SPAM'] # => ['15', '20', '10000']
-
# h['X-Mail-SPAM'] = nil
-
# h['X-Mail-SPAM'] # => nil
-
1
def []=(name, value)
-
30
name = dasherize(name)
-
30
fn = name.downcase
-
30
selected = select_field_for(fn)
-
-
case
-
# User wants to delete the field
-
when !selected.blank? && value == nil
-
fields.delete_if { |f| selected.include?(f) }
-
-
# User wants to change the field
-
when !selected.blank? && limited_field?(fn)
-
4
selected.first.update(fn, value)
-
-
# User wants to create the field
-
else
-
# Need to insert in correct order for trace fields
-
26
self.fields << Field.new(name.to_s, value, charset)
-
30
end
-
end
-
-
1
def charset
-
26
params = self[:content_type].parameters rescue nil
-
26
if params
-
14
params[:charset]
-
else
-
12
@charset
-
end
-
end
-
-
1
def charset=(val)
-
8
params = self[:content_type].parameters rescue nil
-
8
if params
-
4
params[:charset] = val
-
end
-
8
@charset = val
-
end
-
-
1
LIMITED_FIELDS = %w[ date from sender reply-to to cc bcc
-
message-id in-reply-to references subject
-
return-path content-type mime-version
-
content-transfer-encoding content-description
-
content-id content-disposition content-location]
-
-
1
def encoded
-
4
buffer = ''
-
4
fields.each do |field|
-
26
buffer << field.encoded
-
end
-
4
buffer
-
end
-
-
1
def to_s
-
encoded
-
end
-
-
1
def decoded
-
raise NoMethodError, 'Can not decode an entire header as there could be character set conflicts, try calling #decoded on the various fields.'
-
end
-
-
1
def field_summary
-
fields.map { |f| "<#{f.name}: #{f.value}>" }.join(", ")
-
end
-
-
# Returns true if the header has a Message-ID defined (empty or not)
-
1
def has_message_id?
-
34
!fields.select { |f| f.responsible_for?('Message-ID') }.empty?
-
end
-
-
# Returns true if the header has a Content-ID defined (empty or not)
-
1
def has_content_id?
-
18
!fields.select { |f| f.responsible_for?('Content-ID') }.empty?
-
end
-
-
# Returns true if the header has a Date defined (empty or not)
-
1
def has_date?
-
36
!fields.select { |f| f.responsible_for?('Date') }.empty?
-
end
-
-
# Returns true if the header has a MIME version defined (empty or not)
-
1
def has_mime_version?
-
40
!fields.select { |f| f.responsible_for?('Mime-Version') }.empty?
-
end
-
-
1
private
-
-
1
def raw_source=(val)
-
4
@raw_source = val
-
end
-
-
# 2.2.3. Long Header Fields
-
#
-
# The process of moving from this folded multiple-line representation
-
# of a header field to its single line representation is called
-
# "unfolding". Unfolding is accomplished by simply removing any CRLF
-
# that is immediately followed by WSP. Each header field should be
-
# treated in its unfolded form for further syntactic and semantic
-
# evaluation.
-
1
def unfold(string)
-
string.gsub(/#{CRLF}#{WSP}+/, ' ').gsub(/#{WSP}+/, ' ')
-
end
-
-
# Returns the header with all the folds removed
-
1
def unfolded_header
-
@unfolded_header ||= unfold(raw_source)
-
end
-
-
# Splits an unfolded and line break cleaned header into individual field
-
# strings.
-
1
def split_header
-
self.fields = unfolded_header.split(CRLF)
-
end
-
-
1
def select_field_for(name)
-
930
fields.select { |f| f.responsible_for?(name.to_s) }
-
end
-
-
1
def limited_field?(name)
-
4
LIMITED_FIELDS.include?(name.to_s.downcase)
-
end
-
-
end
-
end
-
# encoding: utf-8
-
-
# This is an almost cut and paste from ActiveSupport v3.0.6, copied in here so that Mail
-
# itself does not depend on ActiveSupport to avoid versioning conflicts
-
-
1
module Mail
-
1
class IndifferentHash < Hash
-
-
1
def initialize(constructor = {})
-
8
if constructor.is_a?(Hash)
-
8
super()
-
8
update(constructor)
-
else
-
super(constructor)
-
end
-
end
-
-
1
def default(key = nil)
-
4
if key.is_a?(Symbol) && include?(key = key.to_s)
-
self[key]
-
else
-
4
super
-
end
-
end
-
-
1
def self.new_from_hash_copying_default(hash)
-
IndifferentHash.new(hash).tap do |new_hash|
-
new_hash.default = hash.default
-
end
-
end
-
-
1
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
-
1
alias_method :regular_update, :update unless method_defined?(:regular_update)
-
-
# Assigns a new value to the hash:
-
#
-
# hash = HashWithIndifferentAccess.new
-
# hash[:key] = "value"
-
#
-
1
def []=(key, value)
-
6
regular_writer(convert_key(key), convert_value(value))
-
end
-
-
1
alias_method :store, :[]=
-
-
# Updates the instantized hash with values from the second:
-
#
-
# hash_1 = HashWithIndifferentAccess.new
-
# hash_1[:key] = "value"
-
#
-
# hash_2 = HashWithIndifferentAccess.new
-
# hash_2[:key] = "New Value!"
-
#
-
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
-
#
-
1
def update(other_hash)
-
17
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
-
9
self
-
end
-
-
1
alias_method :merge!, :update
-
-
# Checks the hash for a key matching the argument passed in:
-
#
-
# hash = HashWithIndifferentAccess.new
-
# hash["key"] = "value"
-
# hash.key? :key # => true
-
# hash.key? "key" # => true
-
#
-
1
def key?(key)
-
super(convert_key(key))
-
end
-
-
1
alias_method :include?, :key?
-
1
alias_method :has_key?, :key?
-
1
alias_method :member?, :key?
-
-
# Fetches the value for the specified key, same as doing hash[key]
-
1
def fetch(key, *extras)
-
super(convert_key(key), *extras)
-
end
-
-
# Returns an array of the values at the specified indices:
-
#
-
# hash = HashWithIndifferentAccess.new
-
# hash[:a] = "x"
-
# hash[:b] = "y"
-
# hash.values_at("a", "b") # => ["x", "y"]
-
#
-
1
def values_at(*indices)
-
indices.collect {|key| self[convert_key(key)]}
-
end
-
-
# Returns an exact copy of the hash.
-
1
def dup
-
IndifferentHash.new(self)
-
end
-
-
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
-
# Does not overwrite the existing hash.
-
1
def merge(hash)
-
self.dup.update(hash)
-
end
-
-
# Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
-
# This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
-
1
def reverse_merge(other_hash)
-
super self.class.new_from_hash_copying_default(other_hash)
-
end
-
-
1
def reverse_merge!(other_hash)
-
replace(reverse_merge( other_hash ))
-
end
-
-
# Removes a specified key from the hash.
-
1
def delete(key)
-
super(convert_key(key))
-
end
-
-
1
def stringify_keys!; self end
-
1
def stringify_keys; dup end
-
1
def symbolize_keys; to_hash.symbolize_keys end
-
1
def to_options!; self end
-
-
1
def to_hash
-
Hash.new(default).merge!(self)
-
end
-
-
1
protected
-
-
1
def convert_key(key)
-
14
key.kind_of?(Symbol) ? key.to_s : key
-
end
-
-
1
def convert_value(value)
-
14
if value.class == Hash
-
self.class.new_from_hash_copying_default(value)
-
14
elsif value.is_a?(Array)
-
value.dup.replace(value.map { |e| convert_value(e) })
-
else
-
14
value
-
end
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
-
# Allows you to create a new Mail::Message object.
-
#
-
# You can make an email via passing a string or passing a block.
-
#
-
# For example, the following two examples will create the same email
-
# message:
-
#
-
# Creating via a string:
-
#
-
# string = "To: mikel@test.lindsaar.net\r\n"
-
# string << "From: bob@test.lindsaar.net\r\n"
-
# string << "Subject: This is an email\r\n"
-
# string << "\r\n"
-
# string << "This is the body"
-
# Mail.new(string)
-
#
-
# Or creating via a block:
-
#
-
# message = Mail.new do
-
# to 'mikel@test.lindsaar.net'
-
# from 'bob@test.lindsaar.net'
-
# subject 'This is an email'
-
# body 'This is the body'
-
# end
-
#
-
# Or creating via a hash (or hash like object):
-
#
-
# message = Mail.new({:to => 'mikel@test.lindsaar.net',
-
# 'from' => 'bob@test.lindsaar.net',
-
# :subject => 'This is an email',
-
# :body => 'This is the body' })
-
#
-
# Note, the hash keys can be strings or symbols, the passed in object
-
# does not need to be a hash, it just needs to respond to :each_pair
-
# and yield each key value pair.
-
#
-
# As a side note, you can also create a new email through creating
-
# a Mail::Message object directly and then passing in values via string,
-
# symbol or direct method calls. See Mail::Message for more information.
-
#
-
# mail = Mail.new
-
# mail.to = 'mikel@test.lindsaar.net'
-
# mail[:from] = 'bob@test.lindsaar.net'
-
# mail['subject'] = 'This is an email'
-
# mail.body = 'This is the body'
-
1
def self.new(*args, &block)
-
2
Message.new(args, &block)
-
end
-
-
# Sets the default delivery method and retriever method for all new Mail objects.
-
# The delivery_method and retriever_method default to :smtp and :pop3, with defaults
-
# set.
-
#
-
# So sending a new email, if you have an SMTP server running on localhost is
-
# as easy as:
-
#
-
# Mail.deliver do
-
# to 'mikel@test.lindsaar.net'
-
# from 'bob@test.lindsaar.net'
-
# subject 'hi there!'
-
# body 'this is a body'
-
# end
-
#
-
# If you do not specify anything, you will get the following equivalent code set in
-
# every new mail object:
-
#
-
# Mail.defaults do
-
# delivery_method :smtp, { :address => "localhost",
-
# :port => 25,
-
# :domain => 'localhost.localdomain',
-
# :user_name => nil,
-
# :password => nil,
-
# :authentication => nil,
-
# :enable_starttls_auto => true }
-
#
-
# retriever_method :pop3, { :address => "localhost",
-
# :port => 995,
-
# :user_name => nil,
-
# :password => nil,
-
# :enable_ssl => true }
-
# end
-
#
-
# Mail.delivery_method.new #=> Mail::SMTP instance
-
# Mail.retriever_method.new #=> Mail::POP3 instance
-
#
-
# Each mail object inherits the default set in Mail.delivery_method, however, on
-
# a per email basis, you can override the method:
-
#
-
# mail.delivery_method :sendmail
-
#
-
# Or you can override the method and pass in settings:
-
#
-
# mail.delivery_method :sendmail, { :address => 'some.host' }
-
#
-
# You can also just modify the settings:
-
#
-
# mail.delivery_settings = { :address => 'some.host' }
-
#
-
# The passed in hash is just merged against the defaults with +merge!+ and the result
-
# assigned the mail object. So the above example will change only the :address value
-
# of the global smtp_settings to be 'some.host', keeping all other values
-
1
def self.defaults(&block)
-
Configuration.instance.instance_eval(&block)
-
end
-
-
# Returns the delivery method selected, defaults to an instance of Mail::SMTP
-
1
def self.delivery_method
-
4
Configuration.instance.delivery_method
-
end
-
-
# Returns the retriever method selected, defaults to an instance of Mail::POP3
-
1
def self.retriever_method
-
Configuration.instance.retriever_method
-
end
-
-
# Send an email using the default configuration. You do need to set a default
-
# configuration first before you use self.deliver, if you don't, an appropriate
-
# error will be raised telling you to.
-
#
-
# If you do not specify a delivery type, SMTP will be used.
-
#
-
# Mail.deliver do
-
# to 'mikel@test.lindsaar.net'
-
# from 'ada@test.lindsaar.net'
-
# subject 'This is a test email'
-
# body 'Not much to say here'
-
# end
-
#
-
# You can also do:
-
#
-
# mail = Mail.read('email.eml')
-
# mail.deliver!
-
#
-
# And your email object will be created and sent.
-
1
def self.deliver(*args, &block)
-
mail = self.new(args, &block)
-
mail.deliver
-
mail
-
end
-
-
# Find emails from the default retriever
-
# See Mail::Retriever for a complete documentation.
-
1
def self.find(*args, &block)
-
retriever_method.find(*args, &block)
-
end
-
-
# Finds and then deletes retrieved emails from the default retriever
-
# See Mail::Retriever for a complete documentation.
-
1
def self.find_and_delete(*args, &block)
-
retriever_method.find_and_delete(*args, &block)
-
end
-
-
# Receive the first email(s) from the default retriever
-
# See Mail::Retriever for a complete documentation.
-
1
def self.first(*args, &block)
-
retriever_method.first(*args, &block)
-
end
-
-
# Receive the first email(s) from the default retriever
-
# See Mail::Retriever for a complete documentation.
-
1
def self.last(*args, &block)
-
retriever_method.last(*args, &block)
-
end
-
-
# Receive all emails from the default retriever
-
# See Mail::Retriever for a complete documentation.
-
1
def self.all(*args, &block)
-
retriever_method.all(*args, &block)
-
end
-
-
# Reads in an email message from a path and instantiates it as a new Mail::Message
-
1
def self.read(filename)
-
self.new(File.open(filename, 'rb') { |f| f.read })
-
end
-
-
# Delete all emails from the default retriever
-
# See Mail::Retriever for a complete documentation.
-
1
def self.delete_all(*args, &block)
-
retriever_method.delete_all(*args, &block)
-
end
-
-
# Instantiates a new Mail::Message using a string
-
1
def Mail.read_from_string(mail_as_string)
-
Mail.new(mail_as_string)
-
end
-
-
1
def Mail.connection(&block)
-
retriever_method.connection(&block)
-
end
-
-
# Initialize the observers and interceptors arrays
-
1
@@delivery_notification_observers = []
-
1
@@delivery_interceptors = []
-
-
# You can register an object to be informed of every email that is sent through
-
# this method.
-
#
-
# Your object needs to respond to a single method #delivered_email(mail)
-
# which receives the email that is sent.
-
1
def self.register_observer(observer)
-
unless @@delivery_notification_observers.include?(observer)
-
@@delivery_notification_observers << observer
-
end
-
end
-
-
# You can register an object to be given every mail object that will be sent,
-
# before it is sent. So if you want to add special headers or modify any
-
# email that gets sent through the Mail library, you can do so.
-
#
-
# Your object needs to respond to a single method #delivering_email(mail)
-
# which receives the email that is about to be sent. Make your modifications
-
# directly to this object.
-
1
def self.register_interceptor(interceptor)
-
unless @@delivery_interceptors.include?(interceptor)
-
@@delivery_interceptors << interceptor
-
end
-
end
-
-
1
def self.inform_observers(mail)
-
2
@@delivery_notification_observers.each do |observer|
-
observer.delivered_email(mail)
-
end
-
end
-
-
1
def self.inform_interceptors(mail)
-
2
@@delivery_interceptors.each do |interceptor|
-
interceptor.delivering_email(mail)
-
end
-
end
-
-
1
protected
-
-
1
def self.random_tag
-
5
t = Time.now
-
5
sprintf('%x%x_%x%x%d%x',
-
t.to_i, t.tv_usec,
-
$$, Thread.current.object_id.abs, self.uniq, rand(255))
-
end
-
-
1
private
-
-
1
def self.something_random
-
1
(Thread.current.object_id * rand(255) / Time.now.to_f).to_s.slice(-3..-1).to_i
-
end
-
-
1
def self.uniq
-
5
@@uniq += 1
-
end
-
-
1
@@uniq = self.something_random
-
-
end
-
# encoding: utf-8
-
1
require "yaml"
-
-
1
module Mail
-
# The Message class provides a single point of access to all things to do with an
-
# email message.
-
#
-
# You create a new email message by calling the Mail::Message.new method, or just
-
# Mail.new
-
#
-
# A Message object by default has the following objects inside it:
-
#
-
# * A Header object which contains all information and settings of the header of the email
-
# * Body object which contains all parts of the email that are not part of the header, this
-
# includes any attachments, body text, MIME parts etc.
-
#
-
# ==Per RFC2822
-
#
-
# 2.1. General Description
-
#
-
# At the most basic level, a message is a series of characters. A
-
# message that is conformant with this standard is comprised of
-
# characters with values in the range 1 through 127 and interpreted as
-
# US-ASCII characters [ASCII]. For brevity, this document sometimes
-
# refers to this range of characters as simply "US-ASCII characters".
-
#
-
# Note: This standard specifies that messages are made up of characters
-
# in the US-ASCII range of 1 through 127. There are other documents,
-
# specifically the MIME document series [RFC2045, RFC2046, RFC2047,
-
# RFC2048, RFC2049], that extend this standard to allow for values
-
# outside of that range. Discussion of those mechanisms is not within
-
# the scope of this standard.
-
#
-
# Messages are divided into lines of characters. A line is a series of
-
# characters that is delimited with the two characters carriage-return
-
# and line-feed; that is, the carriage return (CR) character (ASCII
-
# value 13) followed immediately by the line feed (LF) character (ASCII
-
# value 10). (The carriage-return/line-feed pair is usually written in
-
# this document as "CRLF".)
-
#
-
# A message consists of header fields (collectively called "the header
-
# of the message") followed, optionally, by a body. The header is a
-
# sequence of lines of characters with special syntax as defined in
-
# this standard. The body is simply a sequence of characters that
-
# follows the header and is separated from the header by an empty line
-
# (i.e., a line with nothing preceding the CRLF).
-
1
class Message
-
-
1
include Patterns
-
1
include Utilities
-
-
# ==Making an email
-
#
-
# You can make an new mail object via a block, passing a string, file or direct assignment.
-
#
-
# ===Making an email via a block
-
#
-
# mail = Mail.new do
-
# from 'mikel@test.lindsaar.net'
-
# to 'you@test.lindsaar.net'
-
# subject 'This is a test email'
-
# body File.read('body.txt')
-
# end
-
#
-
# mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@...
-
#
-
# ===Making an email via passing a string
-
#
-
# mail = Mail.new("To: mikel@test.lindsaar.net\r\nSubject: Hello\r\n\r\nHi there!")
-
# mail.body.to_s #=> 'Hi there!'
-
# mail.subject #=> 'Hello'
-
# mail.to #=> 'mikel@test.lindsaar.net'
-
#
-
# ===Making an email from a file
-
#
-
# mail = Mail.read('path/to/file.eml')
-
# mail.body.to_s #=> 'Hi there!'
-
# mail.subject #=> 'Hello'
-
# mail.to #=> 'mikel@test.lindsaar.net'
-
#
-
# ===Making an email via assignment
-
#
-
# You can assign values to a mail object via four approaches:
-
#
-
# * Message#field_name=(value)
-
# * Message#field_name(value)
-
# * Message#['field_name']=(value)
-
# * Message#[:field_name]=(value)
-
#
-
# Examples:
-
#
-
# mail = Mail.new
-
# mail['from'] = 'mikel@test.lindsaar.net'
-
# mail[:to] = 'you@test.lindsaar.net'
-
# mail.subject 'This is a test email'
-
# mail.body = 'This is a body'
-
#
-
# mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@...
-
#
-
1
def initialize(*args, &block)
-
4
@body = nil
-
4
@body_raw = nil
-
4
@separate_parts = false
-
4
@text_part = nil
-
4
@html_part = nil
-
4
@errors = nil
-
4
@header = nil
-
4
@charset = 'UTF-8'
-
4
@defaulted_charset = true
-
-
4
@perform_deliveries = true
-
4
@raise_delivery_errors = true
-
-
4
@delivery_handler = nil
-
-
4
@delivery_method = Mail.delivery_method.dup
-
-
4
@transport_encoding = Mail::Encodings.get_encoding('7bit')
-
-
4
@mark_for_delete = false
-
-
4
if args.flatten.first.respond_to?(:each_pair)
-
2
init_with_hash(args.flatten.first)
-
else
-
2
init_with_string(args.flatten[0].to_s.strip)
-
end
-
-
4
if block_given?
-
instance_eval(&block)
-
end
-
-
4
self
-
end
-
-
# If you assign a delivery handler, mail will call :deliver_mail on the
-
# object you assign to delivery_handler, it will pass itself as the
-
# single argument.
-
#
-
# If you define a delivery_handler, then you are responsible for the
-
# following actions in the delivery cycle:
-
#
-
# * Appending the mail object to Mail.deliveries as you see fit.
-
# * Checking the mail.perform_deliveries flag to decide if you should
-
# actually call :deliver! the mail object or not.
-
# * Checking the mail.raise_delivery_errors flag to decide if you
-
# should raise delivery errors if they occur.
-
# * Actually calling :deliver! (with the bang) on the mail object to
-
# get it to deliver itself.
-
#
-
# A simplest implementation of a delivery_handler would be
-
#
-
# class MyObject
-
#
-
# def initialize
-
# @mail = Mail.new('To: mikel@test.lindsaar.net')
-
# @mail.delivery_handler = self
-
# end
-
#
-
# attr_accessor :mail
-
#
-
# def deliver_mail(mail)
-
# yield
-
# end
-
# end
-
#
-
# Then doing:
-
#
-
# obj = MyObject.new
-
# obj.mail.deliver
-
#
-
# Would cause Mail to call obj.deliver_mail passing itself as a parameter,
-
# which then can just yield and let Mail do it's own private do_delivery
-
# method.
-
1
attr_accessor :delivery_handler
-
-
# If set to false, mail will go through the motions of doing a delivery,
-
# but not actually call the delivery method or append the mail object to
-
# the Mail.deliveries collection. Useful for testing.
-
#
-
# Mail.deliveries.size #=> 0
-
# mail.delivery_method :smtp
-
# mail.perform_deliveries = false
-
# mail.deliver # Mail::SMTP not called here
-
# Mail.deliveries.size #=> 0
-
#
-
# If you want to test and query the Mail.deliveries collection to see what
-
# mail you sent, you should set perform_deliveries to true and use
-
# the :test mail delivery_method:
-
#
-
# Mail.deliveries.size #=> 0
-
# mail.delivery_method :test
-
# mail.perform_deliveries = true
-
# mail.deliver
-
# Mail.deliveries.size #=> 1
-
#
-
# This setting is ignored by mail (though still available as a flag) if you
-
# define a delivery_handler
-
1
attr_accessor :perform_deliveries
-
-
# If set to false, mail will silently catch and ignore any exceptions
-
# raised through attempting to deliver an email.
-
#
-
# This setting is ignored by mail (though still available as a flag) if you
-
# define a delivery_handler
-
1
attr_accessor :raise_delivery_errors
-
-
1
def register_for_delivery_notification(observer)
-
STDERR.puts("Message#register_for_delivery_notification is deprecated, please call Mail.register_observer instead")
-
Mail.register_observer(observer)
-
end
-
-
1
def inform_observers
-
2
Mail.inform_observers(self)
-
end
-
-
1
def inform_interceptors
-
2
Mail.inform_interceptors(self)
-
end
-
-
# Delivers an mail object.
-
#
-
# Examples:
-
#
-
# mail = Mail.read('file.eml')
-
# mail.deliver
-
1
def deliver
-
2
inform_interceptors
-
2
if delivery_handler
-
4
delivery_handler.deliver_mail(self) { do_delivery }
-
else
-
do_delivery
-
end
-
2
inform_observers
-
2
self
-
end
-
-
# This method bypasses checking perform_deliveries and raise_delivery_errors,
-
# so use with caution.
-
#
-
# It still however fires off the intercepters and calls the observers callbacks if they are defined.
-
#
-
# Returns self
-
1
def deliver!
-
inform_interceptors
-
response = delivery_method.deliver!(self)
-
inform_observers
-
delivery_method.settings[:return_response] ? response : self
-
end
-
-
1
def delivery_method(method = nil, settings = {})
-
4
unless method
-
2
@delivery_method
-
else
-
2
@delivery_method = Configuration.instance.lookup_delivery_method(method).new(settings)
-
end
-
end
-
-
1
def reply(*args, &block)
-
self.class.new.tap do |reply|
-
if message_id
-
bracketed_message_id = "<#{message_id}>"
-
reply.in_reply_to = bracketed_message_id
-
if !references.nil?
-
refs = [references].flatten.map { |r| "<#{r}>" }
-
refs << bracketed_message_id
-
reply.references = refs.join(' ')
-
elsif !in_reply_to.nil? && !in_reply_to.kind_of?(Array)
-
reply.references = "<#{in_reply_to}> #{bracketed_message_id}"
-
end
-
reply.references ||= bracketed_message_id
-
end
-
if subject
-
reply.subject = subject =~ /^Re:/i ? subject : "RE: #{subject}"
-
end
-
if reply_to || from
-
reply.to = self[reply_to ? :reply_to : :from].to_s
-
end
-
if to
-
reply.from = self[:to].formatted.first.to_s
-
end
-
-
unless args.empty?
-
if args.flatten.first.respond_to?(:each_pair)
-
reply.send(:init_with_hash, args.flatten.first)
-
else
-
reply.send(:init_with_string, args.flatten[0].to_s.strip)
-
end
-
end
-
-
if block_given?
-
reply.instance_eval(&block)
-
end
-
end
-
end
-
-
# Provides the operator needed for sort et al.
-
#
-
# Compares this mail object with another mail object, this is done by date, so an
-
# email that is older than another will appear first.
-
#
-
# Example:
-
#
-
# mail1 = Mail.new do
-
# date(Time.now)
-
# end
-
# mail2 = Mail.new do
-
# date(Time.now - 86400) # 1 day older
-
# end
-
# [mail2, mail1].sort #=> [mail2, mail1]
-
1
def <=>(other)
-
if other.nil?
-
1
-
else
-
self.date <=> other.date
-
end
-
end
-
-
# Two emails are the same if they have the same fields and body contents. One
-
# gotcha here is that Mail will insert Message-IDs when calling encoded, so doing
-
# mail1.encoded == mail2.encoded is most probably not going to return what you think
-
# as the assigned Message-IDs by Mail (if not already defined as the same) will ensure
-
# that the two objects are unique, and this comparison will ALWAYS return false.
-
#
-
# So the == operator has been defined like so: Two messages are the same if they have
-
# the same content, ignoring the Message-ID field, unless BOTH emails have a defined and
-
# different Message-ID value, then they are false.
-
#
-
# So, in practice the == operator works like this:
-
#
-
# m1 = Mail.new("Subject: Hello\r\n\r\nHello")
-
# m2 = Mail.new("Subject: Hello\r\n\r\nHello")
-
# m1 == m2 #=> true
-
#
-
# m1 = Mail.new("Subject: Hello\r\n\r\nHello")
-
# m2 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
-
# m1 == m2 #=> true
-
#
-
# m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
-
# m2 = Mail.new("Subject: Hello\r\n\r\nHello")
-
# m1 == m2 #=> true
-
#
-
# m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
-
# m2 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
-
# m1 == m2 #=> true
-
#
-
# m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
-
# m2 = Mail.new("Message-ID: <DIFFERENT@test>\r\nSubject: Hello\r\n\r\nHello")
-
# m1 == m2 #=> false
-
1
def ==(other)
-
return false unless other.respond_to?(:encoded)
-
-
if self.message_id && other.message_id
-
result = (self.encoded == other.encoded)
-
else
-
self_message_id, other_message_id = self.message_id, other.message_id
-
self.message_id, other.message_id = '<temp@test>', '<temp@test>'
-
result = self.encoded == other.encoded
-
self.message_id = "<#{self_message_id}>" if self_message_id
-
other.message_id = "<#{other_message_id}>" if other_message_id
-
result
-
end
-
end
-
-
# Provides access to the raw source of the message as it was when it
-
# was instantiated. This is set at initialization and so is untouched
-
# by the parsers or decoder / encoders
-
#
-
# Example:
-
#
-
# mail = Mail.new('This is an invalid email message')
-
# mail.raw_source #=> "This is an invalid email message"
-
1
def raw_source
-
4
@raw_source
-
end
-
-
# Sets the envelope from for the email
-
1
def set_envelope( val )
-
@raw_envelope = val
-
@envelope = Mail::Envelope.new( val )
-
end
-
-
# The raw_envelope is the From mikel@test.lindsaar.net Mon May 2 16:07:05 2009
-
# type field that you can see at the top of any email that has come
-
# from a mailbox
-
1
def raw_envelope
-
@raw_envelope
-
end
-
-
1
def envelope_from
-
@envelope ? @envelope.from : nil
-
end
-
-
1
def envelope_date
-
@envelope ? @envelope.date : nil
-
end
-
-
# Sets the header of the message object.
-
#
-
# Example:
-
#
-
# mail.header = 'To: mikel@test.lindsaar.net\r\nFrom: Bob@bob.com'
-
# mail.header #=> <#Mail::Header
-
1
def header=(value)
-
2
@header = Mail::Header.new(value, charset)
-
end
-
-
# Returns the header object of the message object. Or, if passed
-
# a parameter sets the value.
-
#
-
# Example:
-
#
-
# mail = Mail::Message.new('To: mikel\r\nFrom: you')
-
# mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
-
#
-
# mail.header #=> nil
-
# mail.header 'To: mikel\r\nFrom: you'
-
# mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
-
1
def header(value = nil)
-
184
value ? self.header = value : @header
-
end
-
-
# Provides a way to set custom headers, by passing in a hash
-
1
def headers(hash = {})
-
hash.each_pair do |k,v|
-
header[k] = v
-
end
-
end
-
-
# Returns a list of parser errors on the header, each field that had an error
-
# will be reparsed as an unstructured field to preserve the data inside, but
-
# will not be used for further processing.
-
#
-
# It returns a nested array of [field_name, value, original_error_message]
-
# per error found.
-
#
-
# Example:
-
#
-
# message = Mail.new("Content-Transfer-Encoding: weirdo\r\n")
-
# message.errors.size #=> 1
-
# message.errors.first[0] #=> "Content-Transfer-Encoding"
-
# message.errors.first[1] #=> "weirdo"
-
# message.errors.first[3] #=> <The original error message exception>
-
#
-
# This is a good first defence on detecting spam by the way. Some spammers send
-
# invalid emails to try and get email parsers to give up parsing them.
-
1
def errors
-
header.errors
-
end
-
-
# Returns the Bcc value of the mail object as an array of strings of
-
# address specs.
-
#
-
# Example:
-
#
-
# mail.bcc = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.bcc #=> ['mikel@test.lindsaar.net']
-
# mail.bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.bcc 'Mikel <mikel@test.lindsaar.net>'
-
# mail.bcc #=> ['mikel@test.lindsaar.net']
-
#
-
# Additionally, you can append new addresses to the returned Array like
-
# object.
-
#
-
# Example:
-
#
-
# mail.bcc 'Mikel <mikel@test.lindsaar.net>'
-
# mail.bcc << 'ada@test.lindsaar.net'
-
# mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def bcc( val = nil )
-
2
default :bcc, val
-
end
-
-
# Sets the Bcc value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.bcc = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.bcc #=> ['mikel@test.lindsaar.net']
-
# mail.bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def bcc=( val )
-
header[:bcc] = val
-
end
-
-
# Returns the Cc value of the mail object as an array of strings of
-
# address specs.
-
#
-
# Example:
-
#
-
# mail.cc = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.cc #=> ['mikel@test.lindsaar.net']
-
# mail.cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.cc 'Mikel <mikel@test.lindsaar.net>'
-
# mail.cc #=> ['mikel@test.lindsaar.net']
-
#
-
# Additionally, you can append new addresses to the returned Array like
-
# object.
-
#
-
# Example:
-
#
-
# mail.cc 'Mikel <mikel@test.lindsaar.net>'
-
# mail.cc << 'ada@test.lindsaar.net'
-
# mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def cc( val = nil )
-
2
default :cc, val
-
end
-
-
# Sets the Cc value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.cc = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.cc #=> ['mikel@test.lindsaar.net']
-
# mail.cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def cc=( val )
-
header[:cc] = val
-
end
-
-
1
def comments( val = nil )
-
default :comments, val
-
end
-
-
1
def comments=( val )
-
header[:comments] = val
-
end
-
-
1
def content_description( val = nil )
-
default :content_description, val
-
end
-
-
1
def content_description=( val )
-
header[:content_description] = val
-
end
-
-
1
def content_disposition( val = nil )
-
2
default :content_disposition, val
-
end
-
-
1
def content_disposition=( val )
-
header[:content_disposition] = val
-
end
-
-
1
def content_id( val = nil )
-
default :content_id, val
-
end
-
-
1
def content_id=( val )
-
header[:content_id] = val
-
end
-
-
1
def content_location( val = nil )
-
2
default :content_location, val
-
end
-
-
1
def content_location=( val )
-
header[:content_location] = val
-
end
-
-
1
def content_transfer_encoding( val = nil )
-
4
default :content_transfer_encoding, val
-
end
-
-
1
def content_transfer_encoding=( val )
-
6
header[:content_transfer_encoding] = val
-
end
-
-
1
def content_type( val = nil )
-
6
default :content_type, val
-
end
-
-
1
def content_type=( val )
-
2
header[:content_type] = val
-
end
-
-
1
def date( val = nil )
-
2
default :date, val
-
end
-
-
1
def date=( val )
-
header[:date] = val
-
end
-
-
1
def transport_encoding( val = nil)
-
2
if val
-
self.transport_encoding = val
-
else
-
2
@transport_encoding
-
end
-
end
-
-
1
def transport_encoding=( val )
-
2
@transport_encoding = Mail::Encodings.get_encoding(val)
-
end
-
-
# Returns the From value of the mail object as an array of strings of
-
# address specs.
-
#
-
# Example:
-
#
-
# mail.from = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.from #=> ['mikel@test.lindsaar.net']
-
# mail.from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.from 'Mikel <mikel@test.lindsaar.net>'
-
# mail.from #=> ['mikel@test.lindsaar.net']
-
#
-
# Additionally, you can append new addresses to the returned Array like
-
# object.
-
#
-
# Example:
-
#
-
# mail.from 'Mikel <mikel@test.lindsaar.net>'
-
# mail.from << 'ada@test.lindsaar.net'
-
# mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def from( val = nil )
-
2
default :from, val
-
end
-
-
# Sets the From value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.from = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.from #=> ['mikel@test.lindsaar.net']
-
# mail.from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def from=( val )
-
header[:from] = val
-
end
-
-
1
def in_reply_to( val = nil )
-
default :in_reply_to, val
-
end
-
-
1
def in_reply_to=( val )
-
header[:in_reply_to] = val
-
end
-
-
1
def keywords( val = nil )
-
default :keywords, val
-
end
-
-
1
def keywords=( val )
-
header[:keywords] = val
-
end
-
-
# Returns the Message-ID of the mail object. Note, per RFC 2822 the Message ID
-
# consists of what is INSIDE the < > usually seen in the mail header, so this method
-
# will return only what is inside.
-
#
-
# Example:
-
#
-
# mail.message_id = '<1234@message.id>'
-
# mail.message_id #=> '1234@message.id'
-
#
-
# Also allows you to set the Message-ID by passing a string as a parameter
-
#
-
# mail.message_id '<1234@message.id>'
-
# mail.message_id #=> '1234@message.id'
-
1
def message_id( val = nil )
-
2
default :message_id, val
-
end
-
-
# Sets the Message-ID. Note, per RFC 2822 the Message ID consists of what is INSIDE
-
# the < > usually seen in the mail header, so this method will return only what is inside.
-
#
-
# mail.message_id = '<1234@message.id>'
-
# mail.message_id #=> '1234@message.id'
-
1
def message_id=( val )
-
header[:message_id] = val
-
end
-
-
# Returns the MIME version of the email as a string
-
#
-
# Example:
-
#
-
# mail.mime_version = '1.0'
-
# mail.mime_version #=> '1.0'
-
#
-
# Also allows you to set the MIME version by passing a string as a parameter.
-
#
-
# Example:
-
#
-
# mail.mime_version '1.0'
-
# mail.mime_version #=> '1.0'
-
1
def mime_version( val = nil )
-
default :mime_version, val
-
end
-
-
# Sets the MIME version of the email by accepting a string
-
#
-
# Example:
-
#
-
# mail.mime_version = '1.0'
-
# mail.mime_version #=> '1.0'
-
1
def mime_version=( val )
-
header[:mime_version] = val
-
end
-
-
1
def received( val = nil )
-
if val
-
header[:received] = val
-
else
-
header[:received]
-
end
-
end
-
-
1
def received=( val )
-
header[:received] = val
-
end
-
-
1
def references( val = nil )
-
default :references, val
-
end
-
-
1
def references=( val )
-
header[:references] = val
-
end
-
-
# Returns the Reply-To value of the mail object as an array of strings of
-
# address specs.
-
#
-
# Example:
-
#
-
# mail.reply_to = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.reply_to #=> ['mikel@test.lindsaar.net']
-
# mail.reply_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.reply_to 'Mikel <mikel@test.lindsaar.net>'
-
# mail.reply_to #=> ['mikel@test.lindsaar.net']
-
#
-
# Additionally, you can append new addresses to the returned Array like
-
# object.
-
#
-
# Example:
-
#
-
# mail.reply_to 'Mikel <mikel@test.lindsaar.net>'
-
# mail.reply_to << 'ada@test.lindsaar.net'
-
# mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def reply_to( val = nil )
-
default :reply_to, val
-
end
-
-
# Sets the Reply-To value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.reply_to = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.reply_to #=> ['mikel@test.lindsaar.net']
-
# mail.reply_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def reply_to=( val )
-
header[:reply_to] = val
-
end
-
-
# Returns the Resent-Bcc value of the mail object as an array of strings of
-
# address specs.
-
#
-
# Example:
-
#
-
# mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_bcc #=> ['mikel@test.lindsaar.net']
-
# mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.resent_bcc 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_bcc #=> ['mikel@test.lindsaar.net']
-
#
-
# Additionally, you can append new addresses to the returned Array like
-
# object.
-
#
-
# Example:
-
#
-
# mail.resent_bcc 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_bcc << 'ada@test.lindsaar.net'
-
# mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def resent_bcc( val = nil )
-
default :resent_bcc, val
-
end
-
-
# Sets the Resent-Bcc value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_bcc #=> ['mikel@test.lindsaar.net']
-
# mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def resent_bcc=( val )
-
header[:resent_bcc] = val
-
end
-
-
# Returns the Resent-Cc value of the mail object as an array of strings of
-
# address specs.
-
#
-
# Example:
-
#
-
# mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_cc #=> ['mikel@test.lindsaar.net']
-
# mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.resent_cc 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_cc #=> ['mikel@test.lindsaar.net']
-
#
-
# Additionally, you can append new addresses to the returned Array like
-
# object.
-
#
-
# Example:
-
#
-
# mail.resent_cc 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_cc << 'ada@test.lindsaar.net'
-
# mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def resent_cc( val = nil )
-
default :resent_cc, val
-
end
-
-
# Sets the Resent-Cc value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_cc #=> ['mikel@test.lindsaar.net']
-
# mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def resent_cc=( val )
-
header[:resent_cc] = val
-
end
-
-
1
def resent_date( val = nil )
-
default :resent_date, val
-
end
-
-
1
def resent_date=( val )
-
header[:resent_date] = val
-
end
-
-
# Returns the Resent-From value of the mail object as an array of strings of
-
# address specs.
-
#
-
# Example:
-
#
-
# mail.resent_from = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_from #=> ['mikel@test.lindsaar.net']
-
# mail.resent_from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.resent_from ['Mikel <mikel@test.lindsaar.net>']
-
# mail.resent_from #=> 'mikel@test.lindsaar.net'
-
#
-
# Additionally, you can append new addresses to the returned Array like
-
# object.
-
#
-
# Example:
-
#
-
# mail.resent_from 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_from << 'ada@test.lindsaar.net'
-
# mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def resent_from( val = nil )
-
default :resent_from, val
-
end
-
-
# Sets the Resent-From value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.resent_from = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_from #=> ['mikel@test.lindsaar.net']
-
# mail.resent_from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def resent_from=( val )
-
header[:resent_from] = val
-
end
-
-
1
def resent_message_id( val = nil )
-
default :resent_message_id, val
-
end
-
-
1
def resent_message_id=( val )
-
header[:resent_message_id] = val
-
end
-
-
# Returns the Resent-Sender value of the mail object, as a single string of an address
-
# spec. A sender per RFC 2822 must be a single address, so you can not append to
-
# this address.
-
#
-
# Example:
-
#
-
# mail.resent_sender = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_sender #=> 'mikel@test.lindsaar.net'
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.resent_sender 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_sender #=> 'mikel@test.lindsaar.net'
-
1
def resent_sender( val = nil )
-
default :resent_sender, val
-
end
-
-
# Sets the Resent-Sender value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.sender = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.sender #=> 'mikel@test.lindsaar.net'
-
1
def resent_sender=( val )
-
header[:resent_sender] = val
-
end
-
-
# Returns the Resent-To value of the mail object as an array of strings of
-
# address specs.
-
#
-
# Example:
-
#
-
# mail.resent_to = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_to #=> ['mikel@test.lindsaar.net']
-
# mail.resent_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.resent_to 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_to #=> ['mikel@test.lindsaar.net']
-
#
-
# Additionally, you can append new addresses to the returned Array like
-
# object.
-
#
-
# Example:
-
#
-
# mail.resent_to 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_to << 'ada@test.lindsaar.net'
-
# mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def resent_to( val = nil )
-
default :resent_to, val
-
end
-
-
# Sets the Resent-To value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.resent_to = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.resent_to #=> ['mikel@test.lindsaar.net']
-
# mail.resent_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def resent_to=( val )
-
header[:resent_to] = val
-
end
-
-
# Returns the return path of the mail object, or sets it if you pass a string
-
1
def return_path( val = nil )
-
default :return_path, val
-
end
-
-
# Sets the return path of the object
-
1
def return_path=( val )
-
header[:return_path] = val
-
end
-
-
# Returns the Sender value of the mail object, as a single string of an address
-
# spec. A sender per RFC 2822 must be a single address.
-
#
-
# Example:
-
#
-
# mail.sender = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.sender #=> 'mikel@test.lindsaar.net'
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.sender 'Mikel <mikel@test.lindsaar.net>'
-
# mail.sender #=> 'mikel@test.lindsaar.net'
-
1
def sender( val = nil )
-
default :sender, val
-
end
-
-
# Sets the Sender value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.sender = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.sender #=> 'mikel@test.lindsaar.net'
-
1
def sender=( val )
-
header[:sender] = val
-
end
-
-
# Returns the decoded value of the subject field, as a single string.
-
#
-
# Example:
-
#
-
# mail.subject = "G'Day mate"
-
# mail.subject #=> "G'Day mate"
-
# mail.subject = '=?UTF-8?Q?This_is_=E3=81=82_string?='
-
# mail.subject #=> "This is あ string"
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.subject "G'Day mate"
-
# mail.subject #=> "G'Day mate"
-
1
def subject( val = nil )
-
2
default :subject, val
-
end
-
-
# Sets the Subject value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.subject = '=?UTF-8?Q?This_is_=E3=81=82_string?='
-
# mail.subject #=> "This is あ string"
-
1
def subject=( val )
-
header[:subject] = val
-
end
-
-
# Returns the To value of the mail object as an array of strings of
-
# address specs.
-
#
-
# Example:
-
#
-
# mail.to = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.to #=> ['mikel@test.lindsaar.net']
-
# mail.to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
#
-
# Also allows you to set the value by passing a value as a parameter
-
#
-
# Example:
-
#
-
# mail.to 'Mikel <mikel@test.lindsaar.net>'
-
# mail.to #=> ['mikel@test.lindsaar.net']
-
#
-
# Additionally, you can append new addresses to the returned Array like
-
# object.
-
#
-
# Example:
-
#
-
# mail.to 'Mikel <mikel@test.lindsaar.net>'
-
# mail.to << 'ada@test.lindsaar.net'
-
# mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def to( val = nil )
-
2
default :to, val
-
end
-
-
# Sets the To value of the mail object, pass in a string of the field
-
#
-
# Example:
-
#
-
# mail.to = 'Mikel <mikel@test.lindsaar.net>'
-
# mail.to #=> ['mikel@test.lindsaar.net']
-
# mail.to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
-
# mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
-
1
def to=( val )
-
header[:to] = val
-
end
-
-
# Returns the default value of the field requested as a symbol.
-
#
-
# Each header field has a :default method which returns the most common use case for
-
# that field, for example, the date field types will return a DateTime object when
-
# sent :default, the subject, or unstructured fields will return a decoded string of
-
# their value, the address field types will return a single addr_spec or an array of
-
# addr_specs if there is more than one.
-
1
def default( sym, val = nil )
-
28
if val
-
header[sym] = val
-
else
-
28
header[sym].default if header[sym]
-
end
-
end
-
-
# Sets the body object of the message object.
-
#
-
# Example:
-
#
-
# mail.body = 'This is the body'
-
# mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
-
#
-
# You can also reset the body of an Message object by setting body to nil
-
#
-
# Example:
-
#
-
# mail.body = 'this is the body'
-
# mail.body.encoded #=> 'this is the body'
-
# mail.body = nil
-
# mail.body.encoded #=> ''
-
#
-
# If you try and set the body of an email that is a multipart email, then instead
-
# of deleting all the parts of your email, mail will add a text/plain part to
-
# your email:
-
#
-
# mail.add_file 'somefilename.png'
-
# mail.parts.length #=> 1
-
# mail.body = "This is a body"
-
# mail.parts.length #=> 2
-
# mail.parts.last.content_type.content_type #=> 'This is a body'
-
1
def body=(value)
-
5
body_lazy(value)
-
end
-
-
# Returns the body of the message object. Or, if passed
-
# a parameter sets the value.
-
#
-
# Example:
-
#
-
# mail = Mail::Message.new('To: mikel\r\n\r\nThis is the body')
-
# mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
-
#
-
# mail.body 'This is another body'
-
# mail.body #=> #<Mail::Body:0x13919c @raw_source="This is anothe...
-
1
def body(value = nil)
-
67
if value
-
self.body = value
-
# add_encoding_to_body
-
else
-
67
process_body_raw if @body_raw
-
67
@body
-
end
-
end
-
-
1
def body_encoding(value)
-
if value.nil?
-
body.encoding
-
else
-
body.encoding = value
-
end
-
end
-
-
1
def body_encoding=(value)
-
body.encoding = value
-
end
-
-
# Returns the list of addresses this message should be sent to by
-
# collecting the addresses off the to, cc and bcc fields.
-
#
-
# Example:
-
#
-
# mail.to = 'mikel@test.lindsaar.net'
-
# mail.cc = 'sam@test.lindsaar.net'
-
# mail.bcc = 'bob@test.lindsaar.net'
-
# mail.destinations.length #=> 3
-
# mail.destinations.first #=> 'mikel@test.lindsaar.net'
-
1
def destinations
-
[to_addrs, cc_addrs, bcc_addrs].compact.flatten
-
end
-
-
# Returns an array of addresses (the encoded value) in the From field,
-
# if no From field, returns an empty array
-
1
def from_addrs
-
from ? [from].flatten : []
-
end
-
-
# Returns an array of addresses (the encoded value) in the To field,
-
# if no To field, returns an empty array
-
1
def to_addrs
-
to ? [to].flatten : []
-
end
-
-
# Returns an array of addresses (the encoded value) in the Cc field,
-
# if no Cc field, returns an empty array
-
1
def cc_addrs
-
cc ? [cc].flatten : []
-
end
-
-
# Returns an array of addresses (the encoded value) in the Bcc field,
-
# if no Bcc field, returns an empty array
-
1
def bcc_addrs
-
bcc ? [bcc].flatten : []
-
end
-
-
# Allows you to add an arbitrary header
-
#
-
# Example:
-
#
-
# mail['foo'] = '1234'
-
# mail['foo'].to_s #=> '1234'
-
1
def []=(name, value)
-
16
if name.to_s == 'body'
-
1
self.body = value
-
15
elsif name.to_s =~ /content[-_]type/i
-
3
header[name] = value
-
12
elsif name.to_s == 'charset'
-
4
self.charset = value
-
else
-
8
header[name] = value
-
end
-
end
-
-
# Allows you to read an arbitrary header
-
#
-
# Example:
-
#
-
# mail['foo'] = '1234'
-
# mail['foo'].to_s #=> '1234'
-
1
def [](name)
-
13
header[underscoreize(name)]
-
end
-
-
# Method Missing in this implementation allows you to set any of the
-
# standard fields directly as you would the "to", "subject" etc.
-
#
-
# Those fields used most often (to, subject et al) are given their
-
# own method for ease of documentation and also to avoid the hook
-
# call to method missing.
-
#
-
# This will only catch the known fields listed in:
-
#
-
# Mail::Field::KNOWN_FIELDS
-
#
-
# as per RFC 2822, any ruby string or method name could pretty much
-
# be a field name, so we don't want to just catch ANYTHING sent to
-
# a message object and interpret it as a header.
-
#
-
# This method provides all three types of header call to set, read
-
# and explicitly set with the = operator
-
#
-
# Examples:
-
#
-
# mail.comments = 'These are some comments'
-
# mail.comments #=> 'These are some comments'
-
#
-
# mail.comments 'These are other comments'
-
# mail.comments #=> 'These are other comments'
-
#
-
#
-
# mail.date = 'Tue, 1 Jul 2003 10:52:37 +0200'
-
# mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
-
#
-
# mail.date 'Tue, 1 Jul 2003 10:52:37 +0200'
-
# mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
-
#
-
#
-
# mail.resent_msg_id = '<1234@resent_msg_id.lindsaar.net>'
-
# mail.resent_msg_id #=> '<1234@resent_msg_id.lindsaar.net>'
-
#
-
# mail.resent_msg_id '<4567@resent_msg_id.lindsaar.net>'
-
# mail.resent_msg_id #=> '<4567@resent_msg_id.lindsaar.net>'
-
1
def method_missing(name, *args, &block)
-
#:nodoc:
-
# Only take the structured fields, as we could take _anything_ really
-
# as it could become an optional field... "but therin lies the dark side"
-
field_name = underscoreize(name).chomp("=")
-
if Mail::Field::KNOWN_FIELDS.include?(field_name)
-
if args.empty?
-
header[field_name]
-
else
-
header[field_name] = args.first
-
end
-
else
-
super # otherwise pass it on
-
end
-
#:startdoc:
-
end
-
-
# Returns an FieldList of all the fields in the header in the order that
-
# they appear in the header
-
1
def header_fields
-
header.fields
-
end
-
-
# Returns true if the message has a message ID field, the field may or may
-
# not have a value, but the field exists or not.
-
1
def has_message_id?
-
6
header.has_message_id?
-
end
-
-
# Returns true if the message has a Date field, the field may or may
-
# not have a value, but the field exists or not.
-
1
def has_date?
-
6
header.has_date?
-
end
-
-
# Returns true if the message has a Date field, the field may or may
-
# not have a value, but the field exists or not.
-
1
def has_mime_version?
-
6
header.has_mime_version?
-
end
-
-
1
def has_content_type?
-
27
tmp = header[:content_type].main_type rescue nil
-
27
!!tmp
-
end
-
-
1
def has_charset?
-
6
tmp = header[:content_type].parameters rescue nil
-
6
!!(has_content_type? && tmp && tmp['charset'])
-
end
-
-
1
def has_content_transfer_encoding?
-
13
header[:content_transfer_encoding] && header[:content_transfer_encoding].errors.blank?
-
end
-
-
1
def has_transfer_encoding? # :nodoc:
-
STDERR.puts(":has_transfer_encoding? is deprecated in Mail 1.4.3. Please use has_content_transfer_encoding?\n#{caller}")
-
has_content_transfer_encoding?
-
end
-
-
# Creates a new empty Message-ID field and inserts it in the correct order
-
# into the Header. The MessageIdField object will automatically generate
-
# a unique message ID if you try and encode it or output it to_s without
-
# specifying a message id.
-
#
-
# It will preserve the message ID you specify if you do.
-
1
def add_message_id(msg_id_val = '')
-
2
header['message-id'] = msg_id_val
-
end
-
-
# Creates a new empty Date field and inserts it in the correct order
-
# into the Header. The DateField object will automatically generate
-
# DateTime.now's date if you try and encode it or output it to_s without
-
# specifying a date yourself.
-
#
-
# It will preserve any date you specify if you do.
-
1
def add_date(date_val = '')
-
4
header['date'] = date_val
-
end
-
-
# Creates a new empty Mime Version field and inserts it in the correct order
-
# into the Header. The MimeVersion object will automatically generate
-
# set itself to '1.0' if you try and encode it or output it to_s without
-
# specifying a version yourself.
-
#
-
# It will preserve any date you specify if you do.
-
1
def add_mime_version(ver_val = '')
-
2
header['mime-version'] = ver_val
-
end
-
-
# Adds a content type and charset if the body is US-ASCII
-
#
-
# Otherwise raises a warning
-
1
def add_content_type
-
header[:content_type] = 'text/plain'
-
end
-
-
# Adds a content type and charset if the body is US-ASCII
-
#
-
# Otherwise raises a warning
-
1
def add_charset
-
if !body.empty?
-
# Only give a warning if this isn't an attachment, has non US-ASCII and the user
-
# has not specified an encoding explicitly.
-
if @defaulted_charset && body.raw_source.not_ascii_only? && !self.attachment?
-
warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\n"
-
STDERR.puts(warning)
-
end
-
header[:content_type].parameters['charset'] = @charset
-
end
-
end
-
-
# Adds a content transfer encoding
-
#
-
# Otherwise raises a warning
-
1
def add_content_transfer_encoding
-
if body.only_us_ascii?
-
header[:content_transfer_encoding] = '7bit'
-
else
-
warning = "Non US-ASCII detected and no content-transfer-encoding defined.\nDefaulting to 8bit, set your own if this is incorrect.\n"
-
STDERR.puts(warning)
-
header[:content_transfer_encoding] = '8bit'
-
end
-
end
-
-
1
def add_transfer_encoding # :nodoc:
-
STDERR.puts(":add_transfer_encoding is deprecated in Mail 1.4.3. Please use add_content_transfer_encoding\n#{caller}")
-
add_content_transfer_encoding
-
end
-
-
1
def transfer_encoding # :nodoc:
-
STDERR.puts(":transfer_encoding is deprecated in Mail 1.4.3. Please use content_transfer_encoding\n#{caller}")
-
content_transfer_encoding
-
end
-
-
# Returns the MIME media type of part we are on, this is taken from the content-type header
-
1
def mime_type
-
content_type ? header[:content_type].string : nil rescue nil
-
end
-
-
1
def message_content_type
-
STDERR.puts(":message_content_type is deprecated in Mail 1.4.3. Please use mime_type\n#{caller}")
-
mime_type
-
end
-
-
# Returns the character set defined in the content type field
-
1
def charset
-
4
if @header
-
2
content_type ? content_type_parameters['charset'] : @charset
-
else
-
2
@charset
-
end
-
end
-
-
# Sets the charset to the supplied value.
-
1
def charset=(value)
-
8
@defaulted_charset = false
-
8
@charset = value
-
8
@header.charset = value
-
end
-
-
# Returns the main content type
-
1
def main_type
-
3
has_content_type? ? header[:content_type].main_type : nil rescue nil
-
end
-
-
# Returns the sub content type
-
1
def sub_type
-
has_content_type? ? header[:content_type].sub_type : nil rescue nil
-
end
-
-
# Returns the content type parameters
-
1
def mime_parameters
-
STDERR.puts(':mime_parameters is deprecated in Mail 1.4.3, please use :content_type_parameters instead')
-
content_type_parameters
-
end
-
-
# Returns the content type parameters
-
1
def content_type_parameters
-
7
has_content_type? ? header[:content_type].parameters : nil rescue nil
-
end
-
-
# Returns true if the message is multipart
-
1
def multipart?
-
5
has_content_type? ? !!(main_type =~ /^multipart$/i) : false
-
end
-
-
# Returns true if the message is a multipart/report
-
1
def multipart_report?
-
multipart? && sub_type =~ /^report$/i
-
end
-
-
# Returns true if the message is a multipart/report; report-type=delivery-status;
-
1
def delivery_status_report?
-
multipart_report? && content_type_parameters['report-type'] =~ /^delivery-status$/i
-
end
-
-
# returns the part in a multipart/report email that has the content-type delivery-status
-
1
def delivery_status_part
-
@delivery_stats_part ||= parts.select { |p| p.delivery_status_report_part? }.first
-
end
-
-
1
def bounced?
-
delivery_status_part and delivery_status_part.bounced?
-
end
-
-
1
def action
-
delivery_status_part and delivery_status_part.action
-
end
-
-
1
def final_recipient
-
delivery_status_part and delivery_status_part.final_recipient
-
end
-
-
1
def error_status
-
delivery_status_part and delivery_status_part.error_status
-
end
-
-
1
def diagnostic_code
-
delivery_status_part and delivery_status_part.diagnostic_code
-
end
-
-
1
def remote_mta
-
delivery_status_part and delivery_status_part.remote_mta
-
end
-
-
1
def retryable?
-
delivery_status_part and delivery_status_part.retryable?
-
end
-
-
# Returns the current boundary for this message part
-
1
def boundary
-
2
content_type_parameters ? content_type_parameters['boundary'] : nil
-
end
-
-
# Returns a parts list object of all the parts in the message
-
1
def parts
-
20
body.parts
-
end
-
-
# Returns an AttachmentsList object, which holds all of the attachments in
-
# the receiver object (either the entier email or a part within) and all
-
# of it's descendants.
-
#
-
# It also allows you to add attachments to the mail object directly, like so:
-
#
-
# mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
-
#
-
# If you do this, then Mail will take the file name and work out the MIME media type
-
# set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
-
# base64 encode the contents of the attachment all for you.
-
#
-
# You can also specify overrides if you want by passing a hash instead of a string:
-
#
-
# mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
-
# :content => File.read('/path/to/filename.jpg')}
-
#
-
# If you want to use a different encoding than Base64, you can pass an encoding in,
-
# but then it is up to you to pass in the content pre-encoded, and don't expect
-
# Mail to know how to decode this data:
-
#
-
# file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
-
# mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
-
# :encoding => 'SpecialEncoding',
-
# :content => file_content }
-
#
-
# You can also search for specific attachments:
-
#
-
# # By Filename
-
# mail.attachments['filename.jpg'] #=> Mail::Part object or nil
-
#
-
# # or by index
-
# mail.attachments[0] #=> Mail::Part (first attachment)
-
#
-
1
def attachments
-
3
parts.attachments
-
end
-
-
1
def has_attachments?
-
3
!attachments.empty?
-
end
-
-
# Accessor for html_part
-
1
def html_part(&block)
-
if block_given?
-
@html_part = Mail::Part.new(&block)
-
add_multipart_alternate_header unless html_part.blank?
-
add_part(@html_part)
-
else
-
@html_part || find_first_mime_type('text/html')
-
end
-
end
-
-
# Accessor for text_part
-
1
def text_part(&block)
-
if block_given?
-
@text_part = Mail::Part.new(&block)
-
add_multipart_alternate_header unless html_part.blank?
-
add_part(@text_part)
-
else
-
@text_part || find_first_mime_type('text/plain')
-
end
-
end
-
-
# Helper to add a html part to a multipart/alternative email. If this and
-
# text_part are both defined in a message, then it will be a multipart/alternative
-
# message and set itself that way.
-
1
def html_part=(msg = nil)
-
if msg
-
@html_part = msg
-
else
-
@html_part = Mail::Part.new('Content-Type: text/html;')
-
end
-
add_multipart_alternate_header unless text_part.blank?
-
add_part(@html_part)
-
end
-
-
# Helper to add a text part to a multipart/alternative email. If this and
-
# html_part are both defined in a message, then it will be a multipart/alternative
-
# message and set itself that way.
-
1
def text_part=(msg = nil)
-
if msg
-
@text_part = msg
-
else
-
@text_part = Mail::Part.new('Content-Type: text/plain;')
-
end
-
add_multipart_alternate_header unless html_part.blank?
-
add_part(@text_part)
-
end
-
-
# Adds a part to the parts list or creates the part list
-
1
def add_part(part)
-
2
if !body.multipart? && !self.body.decoded.blank?
-
@text_part = Mail::Part.new('Content-Type: text/plain;')
-
@text_part.body = body.decoded
-
self.body << @text_part
-
add_multipart_alternate_header
-
end
-
2
add_boundary
-
2
self.body << part
-
end
-
-
# Allows you to add a part in block form to an existing mail message object
-
#
-
# Example:
-
#
-
# mail = Mail.new do
-
# part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
-
# p.part :content_type => "text/plain", :body => "test text\nline #2"
-
# p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
-
# end
-
# end
-
1
def part(params = {})
-
new_part = Part.new(params)
-
yield new_part if block_given?
-
add_part(new_part)
-
end
-
-
# Adds a file to the message. You have two options with this method, you can
-
# just pass in the absolute path to the file you want and Mail will read the file,
-
# get the filename from the path you pass in and guess the MIME media type, or you
-
# can pass in the filename as a string, and pass in the file content as a blob.
-
#
-
# Example:
-
#
-
# m = Mail.new
-
# m.add_file('/path/to/filename.png')
-
#
-
# m = Mail.new
-
# m.add_file(:filename => 'filename.png', :content => File.read('/path/to/file.jpg'))
-
#
-
# Note also that if you add a file to an existing message, Mail will convert that message
-
# to a MIME multipart email, moving whatever plain text body you had into it's own text
-
# plain part.
-
#
-
# Example:
-
#
-
# m = Mail.new do
-
# body 'this is some text'
-
# end
-
# m.multipart? #=> false
-
# m.add_file('/path/to/filename.png')
-
# m.multipart? #=> true
-
# m.parts.first.content_type.content_type #=> 'text/plain'
-
# m.parts.last.content_type.content_type #=> 'image/png'
-
#
-
# See also #attachments
-
1
def add_file(values)
-
convert_to_multipart unless self.multipart? || self.body.decoded.blank?
-
add_multipart_mixed_header
-
if values.is_a?(String)
-
basename = File.basename(values)
-
filedata = File.open(values, 'rb') { |f| f.read }
-
else
-
basename = values[:filename]
-
filedata = values[:content] || File.open(values[:filename], 'rb') { |f| f.read }
-
end
-
self.attachments[basename] = filedata
-
end
-
-
1
def convert_to_multipart
-
text = body.decoded
-
self.body = ''
-
text_part = Mail::Part.new({:content_type => 'text/plain;',
-
:body => text})
-
text_part.charset = charset unless @defaulted_charset
-
self.body << text_part
-
end
-
-
# Encodes the message, calls encode on all it's parts, gets an email message
-
# ready to send
-
1
def ready_to_send!
-
6
identify_and_set_transfer_encoding
-
6
parts.sort!([ "text/plain", "text/enriched", "text/html", "multipart/alternative" ])
-
6
parts.each do |part|
-
2
part.transport_encoding = transport_encoding
-
2
part.ready_to_send!
-
end
-
6
add_required_fields
-
end
-
-
1
def encode!
-
STDERR.puts("Deprecated in 1.1.0 in favour of :ready_to_send! as it is less confusing with encoding and decoding.")
-
ready_to_send!
-
end
-
-
# Outputs an encoded string representation of the mail message including
-
# all headers, attachments, etc. This is an encoded email in US-ASCII,
-
# so it is able to be directly sent to an email server.
-
1
def encoded
-
4
ready_to_send!
-
4
buffer = header.encoded
-
4
buffer << "\r\n"
-
4
buffer << body.encoded(content_transfer_encoding)
-
4
buffer
-
end
-
-
1
def without_attachments!
-
return self unless has_attachments?
-
-
parts.delete_if { |p| p.attachment? }
-
body_raw = if parts.empty?
-
''
-
else
-
body.encoded
-
end
-
-
@body = Mail::Body.new(body_raw)
-
-
self
-
end
-
-
1
def to_yaml(opts = {})
-
hash = {}
-
hash['headers'] = {}
-
header.fields.each do |field|
-
hash['headers'][field.name] = field.value
-
end
-
hash['delivery_handler'] = delivery_handler.to_s if delivery_handler
-
hash['transport_encoding'] = transport_encoding.to_s
-
special_variables = [:@header, :@delivery_handler, :@transport_encoding]
-
if multipart?
-
hash['multipart_body'] = []
-
body.parts.map { |part| hash['multipart_body'] << part.to_yaml }
-
special_variables.push(:@body, :@text_part, :@html_part)
-
end
-
(instance_variables.map(&:to_sym) - special_variables).each do |var|
-
hash[var.to_s] = instance_variable_get(var)
-
end
-
hash.to_yaml(opts)
-
end
-
-
1
def self.from_yaml(str)
-
hash = YAML.load(str)
-
m = self.new(:headers => hash['headers'])
-
hash.delete('headers')
-
hash.each do |k,v|
-
case
-
when k == 'delivery_handler'
-
begin
-
m.delivery_handler = Object.const_get(v) unless v.blank?
-
rescue NameError
-
end
-
when k == 'transport_encoding'
-
m.transport_encoding(v)
-
when k == 'multipart_body'
-
v.map {|part| m.add_part Mail::Part.from_yaml(part) }
-
when k =~ /^@/
-
m.instance_variable_set(k.to_sym, v)
-
end
-
end
-
m
-
end
-
-
1
def self.from_hash(hash)
-
Mail::Message.new(hash)
-
end
-
-
1
def to_s
-
encoded
-
end
-
-
1
def inspect
-
"#<#{self.class}:#{self.object_id}, Multipart: #{multipart?}, Headers: #{header.field_summary}>"
-
end
-
-
1
def decoded
-
case
-
when self.text?
-
decode_body_as_text
-
when self.attachment?
-
decode_body
-
when !self.multipart?
-
body.decoded
-
else
-
raise NoMethodError, 'Can not decode an entire message, try calling #decoded on the various fields and body or parts if it is a multipart message.'
-
end
-
end
-
-
1
def read
-
if self.attachment?
-
decode_body
-
else
-
raise NoMethodError, 'Can not call read on a part unless it is an attachment.'
-
end
-
end
-
-
1
def decode_body
-
body.decoded
-
end
-
-
# Returns true if this part is an attachment,
-
# false otherwise.
-
1
def attachment?
-
2
!!find_attachment
-
end
-
-
# Returns the attachment data if there is any
-
1
def attachment
-
@attachment
-
end
-
-
# Returns the filename of the attachment
-
1
def filename
-
find_attachment
-
end
-
-
1
def all_parts
-
parts.map { |p| [p, p.all_parts] }.flatten
-
end
-
-
1
def find_first_mime_type(mt)
-
all_parts.detect { |p| p.mime_type == mt && !p.attachment? }
-
end
-
-
# Skips the deletion of this message. All other messages
-
# flagged for delete still will be deleted at session close (i.e. when
-
# #find exits). Only has an effect if you're using #find_and_delete
-
# or #find with :delete_after_find set to true.
-
1
def skip_deletion
-
@mark_for_delete = false
-
end
-
-
# Sets whether this message should be deleted at session close (i.e.
-
# after #find). Message will only be deleted if messages are retrieved
-
# using the #find_and_delete method, or by calling #find with
-
# :delete_after_find set to true.
-
1
def mark_for_delete=(value = true)
-
@mark_for_delete = value
-
end
-
-
# Returns whether message will be marked for deletion.
-
# If so, the message will be deleted at session close (i.e. after #find
-
# exits), but only if also using the #find_and_delete method, or by
-
# calling #find with :delete_after_find set to true.
-
#
-
# Side-note: Just to be clear, this method will return true even if
-
# the message hasn't yet been marked for delete on the mail server.
-
# However, if this method returns true, it *will be* marked on the
-
# server after each block yields back to #find or #find_and_delete.
-
1
def is_marked_for_delete?
-
return @mark_for_delete
-
end
-
-
1
def text?
-
has_content_type? ? !!(main_type =~ /^text$/i) : false
-
end
-
-
1
private
-
-
# 2.1. General Description
-
# A message consists of header fields (collectively called "the header
-
# of the message") followed, optionally, by a body. The header is a
-
# sequence of lines of characters with special syntax as defined in
-
# this standard. The body is simply a sequence of characters that
-
# follows the header and is separated from the header by an empty line
-
# (i.e., a line with nothing preceding the CRLF).
-
#
-
# Additionally, I allow for the case where someone might have put whitespace
-
# on the "gap line"
-
1
def parse_message
-
2
header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
-
# index = raw_source.index(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
-
# self.header = (index) ? header_part[0,index] : nil
-
# lazy_body ( [raw_source, index+1])
-
2
self.header = header_part
-
2
self.body = body_part
-
end
-
-
1
def raw_source=(value)
-
4
@raw_source = value.to_crlf
-
end
-
-
# see comments to body=. We take data and process it lazily
-
1
def body_lazy(value)
-
5
process_body_raw if @body_raw && value
-
case
-
when value == nil || value.length<=0
-
2
@body = Mail::Body.new('')
-
2
@body_raw = nil
-
2
add_encoding_to_body
-
when @body && @body.multipart?
-
@body << Mail::Part.new(value)
-
add_encoding_to_body
-
else
-
3
@body_raw = value
-
# process_body_raw
-
5
end
-
end
-
-
-
1
def process_body_raw
-
3
@body = Mail::Body.new(@body_raw)
-
3
@body_raw = nil
-
3
separate_parts if @separate_parts
-
-
3
add_encoding_to_body
-
end
-
-
1
def set_envelope_header
-
2
if match_data = raw_source.to_s.match(/\AFrom\s(#{TEXT}+)#{CRLF}(.*)/m)
-
set_envelope(match_data[1])
-
self.raw_source = match_data[2]
-
end
-
end
-
-
1
def separate_parts
-
body.split!(boundary)
-
end
-
-
1
def add_encoding_to_body
-
5
if has_content_transfer_encoding?
-
@body.encoding = content_transfer_encoding
-
end
-
end
-
-
1
def identify_and_set_transfer_encoding
-
6
if body && body.multipart?
-
1
self.content_transfer_encoding = @transport_encoding
-
else
-
5
self.content_transfer_encoding = body.get_best_encoding(@transport_encoding)
-
end
-
end
-
-
1
def add_required_fields
-
6
add_multipart_mixed_header unless !body.multipart?
-
6
body = nil if body.nil?
-
6
add_message_id unless (has_message_id? || self.class == Mail::Part)
-
6
add_date unless has_date?
-
6
add_mime_version unless has_mime_version?
-
6
add_content_type unless has_content_type?
-
6
add_charset unless has_charset?
-
6
add_content_transfer_encoding unless has_content_transfer_encoding?
-
end
-
-
1
def add_multipart_alternate_header
-
header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
-
header['content_type'].parameters[:charset] = @charset
-
body.boundary = boundary
-
end
-
-
1
def add_boundary
-
2
unless body.boundary && boundary
-
1
header['content-type'] = 'multipart/mixed' unless header['content-type']
-
1
header['content-type'].parameters[:boundary] = ContentTypeField.generate_boundary
-
1
header['content_type'].parameters[:charset] = @charset
-
1
body.boundary = boundary
-
end
-
end
-
-
1
def add_multipart_mixed_header
-
1
unless header['content-type']
-
header['content-type'] = ContentTypeField.with_boundary('multipart/mixed').value
-
header['content_type'].parameters[:charset] = @charset
-
body.boundary = boundary
-
end
-
end
-
-
1
def init_with_hash(hash)
-
2
passed_in_options = IndifferentHash.new(hash)
-
2
self.raw_source = ''
-
-
2
@header = Mail::Header.new
-
2
@body = Mail::Body.new
-
2
@body_raw = nil
-
-
# We need to store the body until last, as we need all headers added first
-
2
body_content = nil
-
-
2
passed_in_options.each_pair do |k,v|
-
6
k = underscoreize(k).to_sym if k.class == String
-
6
if k == :headers
-
self.headers(v)
-
elsif k == :body
-
2
body_content = v
-
else
-
4
self[k] = v
-
end
-
end
-
-
2
if body_content
-
2
self.body = body_content
-
2
if has_content_transfer_encoding?
-
body.encoding = content_transfer_encoding
-
end
-
end
-
end
-
-
1
def init_with_string(string)
-
2
self.raw_source = string
-
2
set_envelope_header
-
2
parse_message
-
2
@separate_parts = multipart?
-
end
-
-
# Returns the filename of the attachment (if it exists) or returns nil
-
1
def find_attachment
-
2
content_type_name = header[:content_type].filename rescue nil
-
2
content_disp_name = header[:content_disposition].filename rescue nil
-
2
content_loc_name = header[:content_location].location rescue nil
-
case
-
when content_type && content_type_name
-
filename = content_type_name
-
when content_disposition && content_disp_name
-
filename = content_disp_name
-
when content_location && content_loc_name
-
filename = content_loc_name
-
else
-
2
filename = nil
-
2
end
-
2
filename = Mail::Encodings.decode_encode(filename, :decode) if filename rescue filename
-
2
filename
-
end
-
-
1
def do_delivery
-
2
begin
-
2
if perform_deliveries
-
2
delivery_method.deliver!(self)
-
end
-
rescue Exception => e # Net::SMTP errors or sendmail pipe errors
-
raise e if raise_delivery_errors
-
end
-
end
-
-
1
def decode_body_as_text
-
body_text = decode_body
-
if charset
-
if RUBY_VERSION < '1.9'
-
require 'iconv'
-
return Iconv.conv("UTF-8//TRANSLIT//IGNORE", charset, body_text)
-
else
-
if encoding = Encoding.find(charset) rescue nil
-
body_text.force_encoding(encoding)
-
return body_text.encode(Encoding::UTF_8)
-
end
-
end
-
end
-
body_text
-
end
-
-
end
-
end
-
1
require 'mail/network/retriever_methods/base'
-
-
1
module Mail
-
1
autoload :SMTP, 'mail/network/delivery_methods/smtp'
-
1
autoload :FileDelivery, 'mail/network/delivery_methods/file_delivery'
-
1
autoload :Sendmail, 'mail/network/delivery_methods/sendmail'
-
1
autoload :Exim, 'mail/network/delivery_methods/exim'
-
1
autoload :SMTPConnection, 'mail/network/delivery_methods/smtp_connection'
-
1
autoload :TestMailer, 'mail/network/delivery_methods/test_mailer'
-
-
1
autoload :POP3, 'mail/network/retriever_methods/pop3'
-
1
autoload :IMAP, 'mail/network/retriever_methods/imap'
-
1
autoload :TestRetriever, 'mail/network/retriever_methods/test_retriever'
-
end
-
1
module Mail
-
-
# FileDelivery class delivers emails into multiple files based on the destination
-
# address. Each file is appended to if it already exists.
-
#
-
# So if you have an email going to fred@test, bob@test, joe@anothertest, and you
-
# set your location path to /path/to/mails then FileDelivery will create the directory
-
# if it does not exist, and put one copy of the email in three files, called
-
# by their message id
-
#
-
# Make sure the path you specify with :location is writable by the Ruby process
-
# running Mail.
-
1
class FileDelivery
-
-
1
if RUBY_VERSION >= '1.9.1'
-
1
require 'fileutils'
-
else
-
require 'ftools'
-
end
-
-
1
def initialize(values)
-
self.settings = { :location => './mails' }.merge!(values)
-
end
-
-
1
attr_accessor :settings
-
-
1
def deliver!(mail)
-
if ::File.respond_to?(:makedirs)
-
::File.makedirs settings[:location]
-
else
-
::FileUtils.mkdir_p settings[:location]
-
end
-
-
mail.destinations.uniq.each do |to|
-
::File.open(::File.join(settings[:location], File.basename(to.to_s)), 'a') { |f| "#{f.write(mail.encoded)}\r\n\r\n" }
-
end
-
end
-
-
end
-
end
-
1
module Mail
-
# A delivery method implementation which sends via sendmail.
-
#
-
# To use this, first find out where the sendmail binary is on your computer,
-
# if you are on a mac or unix box, it is usually in /usr/sbin/sendmail, this will
-
# be your sendmail location.
-
#
-
# Mail.defaults do
-
# delivery_method :sendmail
-
# end
-
#
-
# Or if your sendmail binary is not at '/usr/sbin/sendmail'
-
#
-
# Mail.defaults do
-
# delivery_method :sendmail, :location => '/absolute/path/to/your/sendmail'
-
# end
-
#
-
# Then just deliver the email as normal:
-
#
-
# Mail.deliver do
-
# to 'mikel@test.lindsaar.net'
-
# from 'ada@test.lindsaar.net'
-
# subject 'testing sendmail'
-
# body 'testing sendmail'
-
# end
-
#
-
# Or by calling deliver on a Mail message
-
#
-
# mail = Mail.new do
-
# to 'mikel@test.lindsaar.net'
-
# from 'ada@test.lindsaar.net'
-
# subject 'testing sendmail'
-
# body 'testing sendmail'
-
# end
-
#
-
# mail.deliver!
-
1
class Sendmail
-
-
1
def initialize(values)
-
self.settings = { :location => '/usr/sbin/sendmail',
-
:arguments => '-i -t' }.merge(values)
-
end
-
-
1
attr_accessor :settings
-
-
1
def deliver!(mail)
-
envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
-
return_path = "-f " + '"' + envelope_from.escape_for_shell + '"' if envelope_from
-
-
arguments = [settings[:arguments], return_path].compact.join(" ")
-
-
self.class.call(settings[:location], arguments, mail.destinations.collect(&:escape_for_shell).join(" "), mail)
-
end
-
-
1
def self.call(path, arguments, destinations, mail)
-
IO.popen("#{path} #{arguments} #{destinations}", "w+") do |io|
-
io.puts mail.encoded.to_lf
-
io.flush
-
end
-
end
-
end
-
end
-
1
module Mail
-
# == Sending Email with SMTP
-
#
-
# Mail allows you to send emails using SMTP. This is done by wrapping Net::SMTP in
-
# an easy to use manner.
-
#
-
# === Sending via SMTP server on Localhost
-
#
-
# Sending locally (to a postfix or sendmail server running on localhost) requires
-
# no special setup. Just to Mail.deliver &block or message.deliver! and it will
-
# be sent in this method.
-
#
-
# === Sending via MobileMe
-
#
-
# Mail.defaults do
-
# delivery_method :smtp, { :address => "smtp.me.com",
-
# :port => 587,
-
# :domain => 'your.host.name',
-
# :user_name => '<username>',
-
# :password => '<password>',
-
# :authentication => 'plain',
-
# :enable_starttls_auto => true }
-
# end
-
#
-
# === Sending via GMail
-
#
-
# Mail.defaults do
-
# delivery_method :smtp, { :address => "smtp.gmail.com",
-
# :port => 587,
-
# :domain => 'your.host.name',
-
# :user_name => '<username>',
-
# :password => '<password>',
-
# :authentication => 'plain',
-
# :enable_starttls_auto => true }
-
# end
-
#
-
# === Certificate verification
-
#
-
# When using TLS, some mail servers provide certificates that are self-signed
-
# or whose names do not exactly match the hostname given in the address.
-
# OpenSSL will reject these by default. The best remedy is to use the correct
-
# hostname or update the certificate authorities trusted by your ruby. If
-
# that isn't possible, you can control this behavior with
-
# an :openssl_verify_mode setting. Its value may be either an OpenSSL
-
# verify mode constant (OpenSSL::SSL::VERIFY_NONE), or a string containing
-
# the name of an OpenSSL verify mode (none, peer, client_once,
-
# fail_if_no_peer_cert).
-
#
-
# === Others
-
#
-
# Feel free to send me other examples that were tricky
-
#
-
# === Delivering the email
-
#
-
# Once you have the settings right, sending the email is done by:
-
#
-
# Mail.deliver do
-
# to 'mikel@test.lindsaar.net'
-
# from 'ada@test.lindsaar.net'
-
# subject 'testing sendmail'
-
# body 'testing sendmail'
-
# end
-
#
-
# Or by calling deliver on a Mail message
-
#
-
# mail = Mail.new do
-
# to 'mikel@test.lindsaar.net'
-
# from 'ada@test.lindsaar.net'
-
# subject 'testing sendmail'
-
# body 'testing sendmail'
-
# end
-
#
-
# mail.deliver!
-
1
class SMTP
-
-
1
def initialize(values)
-
1
self.settings = { :address => "localhost",
-
:port => 25,
-
:domain => 'localhost.localdomain',
-
:user_name => nil,
-
:password => nil,
-
:authentication => nil,
-
:enable_starttls_auto => true,
-
:openssl_verify_mode => nil,
-
:ssl => nil,
-
:tls => nil
-
}.merge!(values)
-
end
-
-
1
attr_accessor :settings
-
-
# Send the message via SMTP.
-
# The from and to attributes are optional. If not set, they are retrieve from the Message.
-
1
def deliver!(mail)
-
-
# Set the envelope from to be either the return-path, the sender or the first from address
-
envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
-
if envelope_from.blank?
-
raise ArgumentError.new('A sender (Return-Path, Sender or From) required to send a message')
-
end
-
-
destinations ||= mail.destinations if mail.respond_to?(:destinations) && mail.destinations
-
if destinations.blank?
-
raise ArgumentError.new('At least one recipient (To, Cc or Bcc) is required to send a message')
-
end
-
-
message ||= mail.encoded if mail.respond_to?(:encoded)
-
if message.blank?
-
raise ArgumentError.new('A encoded content is required to send a message')
-
end
-
-
smtp = Net::SMTP.new(settings[:address], settings[:port])
-
if settings[:tls] || settings[:ssl]
-
if smtp.respond_to?(:enable_tls)
-
unless settings[:openssl_verify_mode]
-
smtp.enable_tls
-
else
-
openssl_verify_mode = settings[:openssl_verify_mode]
-
if openssl_verify_mode.kind_of?(String)
-
openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{openssl_verify_mode.upcase}".constantize
-
end
-
context = Net::SMTP.default_ssl_context
-
context.verify_mode = openssl_verify_mode
-
smtp.enable_tls(context)
-
end
-
end
-
elsif settings[:enable_starttls_auto]
-
if smtp.respond_to?(:enable_starttls_auto)
-
unless settings[:openssl_verify_mode]
-
smtp.enable_starttls_auto
-
else
-
openssl_verify_mode = settings[:openssl_verify_mode]
-
if openssl_verify_mode.kind_of?(String)
-
openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{openssl_verify_mode.upcase}".constantize
-
end
-
context = Net::SMTP.default_ssl_context
-
context.verify_mode = openssl_verify_mode
-
smtp.enable_starttls_auto(context)
-
end
-
end
-
end
-
-
response = nil
-
smtp.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication]) do |smtp_obj|
-
response = smtp_obj.sendmail(message, envelope_from, destinations)
-
end
-
-
return settings[:return_response] ? response : self
-
end
-
-
-
end
-
end
-
1
module Mail
-
# The TestMailer is a bare bones mailer that does nothing. It is useful
-
# when you are testing.
-
#
-
# It also provides a template of the minimum methods you require to implement
-
# if you want to make a custom mailer for Mail
-
1
class TestMailer
-
-
# Provides a store of all the emails sent with the TestMailer so you can check them.
-
1
def TestMailer.deliveries
-
22
@@deliveries ||= []
-
end
-
-
# Allows you to over write the default deliveries store from an array to some
-
# other object. If you just want to clear the store,
-
# call TestMailer.deliveries.clear.
-
#
-
# If you place another object here, please make sure it responds to:
-
#
-
# * << (message)
-
# * clear
-
# * length
-
# * size
-
# * and other common Array methods
-
1
def TestMailer.deliveries=(val)
-
17
@@deliveries = val
-
end
-
-
1
def initialize(values)
-
2
@settings = {}
-
end
-
-
1
attr_accessor :settings
-
-
1
def deliver!(mail)
-
2
Mail::TestMailer.deliveries << mail
-
end
-
-
end
-
end
-
# encoding: utf-8
-
-
1
module Mail
-
-
1
class Retriever
-
-
# Get the oldest received email(s)
-
#
-
# Possible options:
-
# count: number of emails to retrieve. The default value is 1.
-
# order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
-
#
-
1
def first(options = {}, &block)
-
options ||= {}
-
options[:what] = :first
-
options[:count] ||= 1
-
find(options, &block)
-
end
-
-
# Get the most recent received email(s)
-
#
-
# Possible options:
-
# count: number of emails to retrieve. The default value is 1.
-
# order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
-
#
-
1
def last(options = {}, &block)
-
options ||= {}
-
options[:what] = :last
-
options[:count] ||= 1
-
find(options, &block)
-
end
-
-
# Get all emails.
-
#
-
# Possible options:
-
# order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
-
#
-
1
def all(options = {}, &block)
-
options ||= {}
-
options[:count] = :all
-
find(options, &block)
-
end
-
-
# Find emails in the mailbox, and then deletes them. Without any options, the
-
# five last received emails are returned.
-
#
-
# Possible options:
-
# what: last or first emails. The default is :first.
-
# order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
-
# count: number of emails to retrieve. The default value is 10. A value of 1 returns an
-
# instance of Message, not an array of Message instances.
-
# delete_after_find: flag for whether to delete each retreived email after find. Default
-
# is true. Call #find if you would like this to default to false.
-
#
-
1
def find_and_delete(options = {}, &block)
-
options ||= {}
-
options[:delete_after_find] ||= true
-
find(options, &block)
-
end
-
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module AddressLists
-
1
include Treetop::Runtime
-
-
1
def root
-
4
@root ||= :primary_address
-
end
-
-
1
include RFC2822
-
-
1
module PrimaryAddress0
-
1
def addresses
-
8
([first_addr] + other_addr.elements.map { |o| o.addr_value }).reject { |e| e.empty? }
-
end
-
end
-
-
1
module PrimaryAddress1
-
1
def addresses
-
[first_addr] + other_addr.elements.map { |o| o.addr_value }
-
end
-
end
-
-
1
def _nt_primary_address
-
4
start_index = index
-
4
if node_cache[:primary_address].has_key?(index)
-
cached = node_cache[:primary_address][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
r1 = _nt_address_list
-
4
r1.extend(PrimaryAddress0)
-
4
if r1
-
4
r0 = r1
-
else
-
r2 = _nt_obs_addr_list
-
r2.extend(PrimaryAddress1)
-
if r2
-
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
4
node_cache[:primary_address][start_index] = r0
-
-
4
r0
-
end
-
-
end
-
-
1
class AddressListsParser < Treetop::Runtime::CompiledParser
-
1
include AddressLists
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module ContentDisposition
-
1
include Treetop::Runtime
-
-
1
def root
-
@root ||= :content_disposition
-
end
-
-
1
include RFC2822
-
-
1
include RFC2045
-
-
1
module ContentDisposition0
-
1
def CFWS1
-
elements[0]
-
end
-
-
1
def parameter
-
elements[2]
-
end
-
-
1
def CFWS2
-
elements[3]
-
end
-
end
-
-
1
module ContentDisposition1
-
1
def disposition_type
-
elements[0]
-
end
-
-
1
def param_hashes
-
elements[1]
-
end
-
end
-
-
1
module ContentDisposition2
-
1
def parameters
-
param_hashes.elements.map do |param|
-
param.parameter.param_hash
-
end
-
end
-
end
-
-
1
def _nt_content_disposition
-
start_index = index
-
if node_cache[:content_disposition].has_key?(index)
-
cached = node_cache[:content_disposition][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_disposition_type
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
i3, s3 = index, []
-
r4 = _nt_CFWS
-
s3 << r4
-
if r4
-
if has_terminal?(";", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(";")
-
r5 = nil
-
end
-
s3 << r5
-
if r5
-
r6 = _nt_parameter
-
s3 << r6
-
if r6
-
r7 = _nt_CFWS
-
s3 << r7
-
end
-
end
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(ContentDisposition0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ContentDisposition1)
-
r0.extend(ContentDisposition2)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:content_disposition][start_index] = r0
-
-
r0
-
end
-
-
1
module DispositionType0
-
end
-
-
1
module DispositionType1
-
end
-
-
1
def _nt_disposition_type
-
start_index = index
-
if node_cache[:disposition_type].has_key?(index)
-
cached = node_cache[:disposition_type][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
if has_terminal?('\G[iI]', true, index)
-
r2 = true
-
@index += 1
-
else
-
r2 = nil
-
end
-
s1 << r2
-
if r2
-
if has_terminal?('\G[nN]', true, index)
-
r3 = true
-
@index += 1
-
else
-
r3 = nil
-
end
-
s1 << r3
-
if r3
-
if has_terminal?('\G[lL]', true, index)
-
r4 = true
-
@index += 1
-
else
-
r4 = nil
-
end
-
s1 << r4
-
if r4
-
if has_terminal?('\G[iI]', true, index)
-
r5 = true
-
@index += 1
-
else
-
r5 = nil
-
end
-
s1 << r5
-
if r5
-
if has_terminal?('\G[nN]', true, index)
-
r6 = true
-
@index += 1
-
else
-
r6 = nil
-
end
-
s1 << r6
-
if r6
-
if has_terminal?('\G[eE]', true, index)
-
r7 = true
-
@index += 1
-
else
-
r7 = nil
-
end
-
s1 << r7
-
end
-
end
-
end
-
end
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(DispositionType0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
i8, s8 = index, []
-
if has_terminal?('\G[aA]', true, index)
-
r9 = true
-
@index += 1
-
else
-
r9 = nil
-
end
-
s8 << r9
-
if r9
-
if has_terminal?('\G[tT]', true, index)
-
r10 = true
-
@index += 1
-
else
-
r10 = nil
-
end
-
s8 << r10
-
if r10
-
if has_terminal?('\G[tT]', true, index)
-
r11 = true
-
@index += 1
-
else
-
r11 = nil
-
end
-
s8 << r11
-
if r11
-
if has_terminal?('\G[aA]', true, index)
-
r12 = true
-
@index += 1
-
else
-
r12 = nil
-
end
-
s8 << r12
-
if r12
-
if has_terminal?('\G[cC]', true, index)
-
r13 = true
-
@index += 1
-
else
-
r13 = nil
-
end
-
s8 << r13
-
if r13
-
if has_terminal?('\G[hH]', true, index)
-
r14 = true
-
@index += 1
-
else
-
r14 = nil
-
end
-
s8 << r14
-
if r14
-
if has_terminal?('\G[mM]', true, index)
-
r15 = true
-
@index += 1
-
else
-
r15 = nil
-
end
-
s8 << r15
-
if r15
-
if has_terminal?('\G[eE]', true, index)
-
r16 = true
-
@index += 1
-
else
-
r16 = nil
-
end
-
s8 << r16
-
if r16
-
if has_terminal?('\G[nN]', true, index)
-
r17 = true
-
@index += 1
-
else
-
r17 = nil
-
end
-
s8 << r17
-
if r17
-
if has_terminal?('\G[tT]', true, index)
-
r18 = true
-
@index += 1
-
else
-
r18 = nil
-
end
-
s8 << r18
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
if s8.last
-
r8 = instantiate_node(SyntaxNode,input, i8...index, s8)
-
r8.extend(DispositionType1)
-
else
-
@index = i8
-
r8 = nil
-
end
-
if r8
-
r0 = r8
-
else
-
r19 = _nt_extension_token
-
if r19
-
r0 = r19
-
else
-
if has_terminal?('', false, index)
-
r20 = instantiate_node(SyntaxNode,input, index...(index + 0))
-
@index += 0
-
else
-
terminal_parse_failure('')
-
r20 = nil
-
end
-
if r20
-
r0 = r20
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
-
node_cache[:disposition_type][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_extension_token
-
start_index = index
-
if node_cache[:extension_token].has_key?(index)
-
cached = node_cache[:extension_token][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_ietf_token
-
if r1
-
r0 = r1
-
else
-
r2 = _nt_custom_x_token
-
if r2
-
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:extension_token][start_index] = r0
-
-
r0
-
end
-
-
1
module Parameter0
-
1
def attr
-
elements[1]
-
end
-
-
1
def val
-
elements[3]
-
end
-
-
end
-
-
1
module Parameter1
-
1
def param_hash
-
{attr.text_value => val.text_value}
-
end
-
end
-
-
1
def _nt_parameter
-
start_index = index
-
if node_cache[:parameter].has_key?(index)
-
cached = node_cache[:parameter][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
r3 = _nt_attribute
-
s0 << r3
-
if r3
-
if has_terminal?("=", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("=")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_value
-
s0 << r5
-
if r5
-
r7 = _nt_CFWS
-
if r7
-
r6 = r7
-
else
-
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Parameter0)
-
r0.extend(Parameter1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:parameter][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_attribute
-
start_index = index
-
if node_cache[:attribute].has_key?(index)
-
cached = node_cache[:attribute][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
s0, i0 = [], index
-
loop do
-
r1 = _nt_token
-
if r1
-
s0 << r1
-
else
-
break
-
end
-
end
-
if s0.empty?
-
@index = i0
-
r0 = nil
-
else
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
end
-
-
node_cache[:attribute][start_index] = r0
-
-
r0
-
end
-
-
1
module Value0
-
1
def text_value
-
quoted_content.text_value
-
end
-
end
-
-
1
def _nt_value
-
start_index = index
-
if node_cache[:value].has_key?(index)
-
cached = node_cache[:value][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_quoted_string
-
r1.extend(Value0)
-
if r1
-
r0 = r1
-
else
-
s2, i2 = [], index
-
loop do
-
i3 = index
-
r4 = _nt_token
-
if r4
-
r3 = r4
-
else
-
if has_terminal?('\G[\\x3d]', true, index)
-
r5 = true
-
@index += 1
-
else
-
r5 = nil
-
end
-
if r5
-
r3 = r5
-
else
-
@index = i3
-
r3 = nil
-
end
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
if s2.empty?
-
@index = i2
-
r2 = nil
-
else
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
end
-
if r2
-
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:value][start_index] = r0
-
-
r0
-
end
-
-
end
-
-
1
class ContentDispositionParser < Treetop::Runtime::CompiledParser
-
1
include ContentDisposition
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module ContentLocation
-
1
include Treetop::Runtime
-
-
1
def root
-
@root ||= :primary
-
end
-
-
1
include RFC2822
-
-
1
include RFC2045
-
-
1
module Primary0
-
1
def CFWS1
-
elements[0]
-
end
-
-
1
def location
-
elements[1]
-
end
-
-
1
def CFWS2
-
elements[2]
-
end
-
end
-
-
1
def _nt_primary
-
start_index = index
-
if node_cache[:primary].has_key?(index)
-
cached = node_cache[:primary][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_CFWS
-
s0 << r1
-
if r1
-
r2 = _nt_location
-
s0 << r2
-
if r2
-
r3 = _nt_CFWS
-
s0 << r3
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Primary0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:primary][start_index] = r0
-
-
r0
-
end
-
-
1
module Location0
-
1
def text_value
-
quoted_content.text_value
-
end
-
end
-
-
1
def _nt_location
-
start_index = index
-
if node_cache[:location].has_key?(index)
-
cached = node_cache[:location][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_quoted_string
-
r1.extend(Location0)
-
if r1
-
r0 = r1
-
else
-
s2, i2 = [], index
-
loop do
-
i3 = index
-
r4 = _nt_token
-
if r4
-
r3 = r4
-
else
-
if has_terminal?('\G[\\x3d]', true, index)
-
r5 = true
-
@index += 1
-
else
-
r5 = nil
-
end
-
if r5
-
r3 = r5
-
else
-
@index = i3
-
r3 = nil
-
end
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
if s2.empty?
-
@index = i2
-
r2 = nil
-
else
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
end
-
if r2
-
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:location][start_index] = r0
-
-
r0
-
end
-
-
end
-
-
1
class ContentLocationParser < Treetop::Runtime::CompiledParser
-
1
include ContentLocation
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module ContentTransferEncoding
-
1
include Treetop::Runtime
-
-
1
def root
-
6
@root ||= :primary
-
end
-
-
1
include RFC2822
-
-
1
include RFC2045
-
-
1
module Primary0
-
1
def CFWS1
-
elements[0]
-
end
-
-
1
def encoding
-
6
elements[1]
-
end
-
-
1
def CFWS2
-
elements[2]
-
end
-
-
1
def CFWS3
-
elements[4]
-
end
-
end
-
-
1
def _nt_primary
-
6
start_index = index
-
6
if node_cache[:primary].has_key?(index)
-
cached = node_cache[:primary][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
6
i0, s0 = index, []
-
6
r1 = _nt_CFWS
-
6
s0 << r1
-
6
if r1
-
6
r2 = _nt_encoding
-
6
s0 << r2
-
6
if r2
-
6
r3 = _nt_CFWS
-
6
s0 << r3
-
6
if r3
-
6
if has_terminal?(";", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
6
terminal_parse_failure(";")
-
6
r5 = nil
-
end
-
6
if r5
-
r4 = r5
-
else
-
6
r4 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
6
s0 << r4
-
6
if r4
-
6
r6 = _nt_CFWS
-
6
s0 << r6
-
end
-
end
-
end
-
end
-
6
if s0.last
-
6
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
6
r0.extend(Primary0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
6
node_cache[:primary][start_index] = r0
-
-
6
r0
-
end
-
-
1
module Encoding0
-
1
def ietf_token
-
6
elements[0]
-
end
-
-
end
-
-
1
module Encoding1
-
1
def text_value
-
6
ietf_token.text_value
-
end
-
end
-
-
1
def _nt_encoding
-
6
start_index = index
-
6
if node_cache[:encoding].has_key?(index)
-
cached = node_cache[:encoding][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
6
i0 = index
-
6
i1, s1 = index, []
-
6
r2 = _nt_ietf_token
-
6
s1 << r2
-
6
if r2
-
6
if has_terminal?("s", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
6
terminal_parse_failure("s")
-
6
r4 = nil
-
end
-
6
if r4
-
r3 = r4
-
else
-
6
r3 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
6
s1 << r3
-
end
-
6
if s1.last
-
6
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
6
r1.extend(Encoding0)
-
6
r1.extend(Encoding1)
-
else
-
@index = i1
-
r1 = nil
-
end
-
6
if r1
-
6
r0 = r1
-
else
-
r5 = _nt_custom_x_token
-
if r5
-
r0 = r5
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
6
node_cache[:encoding][start_index] = r0
-
-
6
r0
-
end
-
-
end
-
-
1
class ContentTransferEncodingParser < Treetop::Runtime::CompiledParser
-
1
include ContentTransferEncoding
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module ContentType
-
1
include Treetop::Runtime
-
-
1
def root
-
6
@root ||= :content_type
-
end
-
-
1
include RFC2822
-
-
1
include RFC2045
-
-
1
module ContentType0
-
1
def CFWS1
-
elements[0]
-
end
-
-
1
def parameter
-
2
elements[2]
-
end
-
-
1
def CFWS2
-
elements[3]
-
end
-
end
-
-
1
module ContentType1
-
1
def main_type
-
6
elements[0]
-
end
-
-
1
def sub_type
-
6
elements[2]
-
end
-
-
1
def param_hashes
-
6
elements[3]
-
end
-
end
-
-
1
module ContentType2
-
1
def parameters
-
6
param_hashes.elements.map do |param|
-
2
param.parameter.param_hash
-
end
-
end
-
end
-
-
1
def _nt_content_type
-
6
start_index = index
-
6
if node_cache[:content_type].has_key?(index)
-
cached = node_cache[:content_type][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
6
i0, s0 = index, []
-
6
r1 = _nt_main_type
-
6
s0 << r1
-
6
if r1
-
6
if has_terminal?("/", false, index)
-
6
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
6
@index += 1
-
else
-
terminal_parse_failure("/")
-
r2 = nil
-
end
-
6
s0 << r2
-
6
if r2
-
6
r3 = _nt_sub_type
-
6
s0 << r3
-
6
if r3
-
6
s4, i4 = [], index
-
6
loop do
-
8
i5, s5 = index, []
-
8
r6 = _nt_CFWS
-
8
s5 << r6
-
8
if r6
-
8
if has_terminal?(";", false, index)
-
2
r8 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
2
@index += 1
-
else
-
6
terminal_parse_failure(";")
-
6
r8 = nil
-
end
-
8
if r8
-
2
r7 = r8
-
else
-
6
r7 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
8
s5 << r7
-
8
if r7
-
8
r9 = _nt_parameter
-
8
s5 << r9
-
8
if r9
-
2
r10 = _nt_CFWS
-
2
s5 << r10
-
end
-
end
-
end
-
8
if s5.last
-
2
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
2
r5.extend(ContentType0)
-
else
-
6
@index = i5
-
6
r5 = nil
-
end
-
8
if r5
-
2
s4 << r5
-
else
-
6
break
-
end
-
end
-
6
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
6
s0 << r4
-
end
-
end
-
end
-
6
if s0.last
-
6
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
6
r0.extend(ContentType1)
-
6
r0.extend(ContentType2)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
6
node_cache[:content_type][start_index] = r0
-
-
6
r0
-
end
-
-
1
def _nt_main_type
-
6
start_index = index
-
6
if node_cache[:main_type].has_key?(index)
-
cached = node_cache[:main_type][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
6
i0 = index
-
6
r1 = _nt_discrete_type
-
6
if r1
-
4
r0 = r1
-
else
-
2
r2 = _nt_composite_type
-
2
if r2
-
2
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
6
node_cache[:main_type][start_index] = r0
-
-
6
r0
-
end
-
-
1
module DiscreteType0
-
end
-
-
1
module DiscreteType1
-
end
-
-
1
module DiscreteType2
-
end
-
-
1
module DiscreteType3
-
end
-
-
1
module DiscreteType4
-
end
-
-
1
def _nt_discrete_type
-
6
start_index = index
-
6
if node_cache[:discrete_type].has_key?(index)
-
cached = node_cache[:discrete_type][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
6
i0 = index
-
6
i1, s1 = index, []
-
6
if has_terminal?('\G[tT]', true, index)
-
4
r2 = true
-
4
@index += 1
-
else
-
2
r2 = nil
-
end
-
6
s1 << r2
-
6
if r2
-
4
if has_terminal?('\G[eE]', true, index)
-
4
r3 = true
-
4
@index += 1
-
else
-
r3 = nil
-
end
-
4
s1 << r3
-
4
if r3
-
4
if has_terminal?('\G[xX]', true, index)
-
4
r4 = true
-
4
@index += 1
-
else
-
r4 = nil
-
end
-
4
s1 << r4
-
4
if r4
-
4
if has_terminal?('\G[tT]', true, index)
-
4
r5 = true
-
4
@index += 1
-
else
-
r5 = nil
-
end
-
4
s1 << r5
-
end
-
end
-
end
-
6
if s1.last
-
4
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
4
r1.extend(DiscreteType0)
-
else
-
2
@index = i1
-
2
r1 = nil
-
end
-
6
if r1
-
4
r0 = r1
-
else
-
2
i6, s6 = index, []
-
2
if has_terminal?('\G[iI]', true, index)
-
r7 = true
-
@index += 1
-
else
-
2
r7 = nil
-
end
-
2
s6 << r7
-
2
if r7
-
if has_terminal?('\G[mM]', true, index)
-
r8 = true
-
@index += 1
-
else
-
r8 = nil
-
end
-
s6 << r8
-
if r8
-
if has_terminal?('\G[aA]', true, index)
-
r9 = true
-
@index += 1
-
else
-
r9 = nil
-
end
-
s6 << r9
-
if r9
-
if has_terminal?('\G[gG]', true, index)
-
r10 = true
-
@index += 1
-
else
-
r10 = nil
-
end
-
s6 << r10
-
if r10
-
if has_terminal?('\G[eE]', true, index)
-
r11 = true
-
@index += 1
-
else
-
r11 = nil
-
end
-
s6 << r11
-
end
-
end
-
end
-
end
-
2
if s6.last
-
r6 = instantiate_node(SyntaxNode,input, i6...index, s6)
-
r6.extend(DiscreteType1)
-
else
-
2
@index = i6
-
2
r6 = nil
-
end
-
2
if r6
-
r0 = r6
-
else
-
2
i12, s12 = index, []
-
2
if has_terminal?('\G[aA]', true, index)
-
r13 = true
-
@index += 1
-
else
-
2
r13 = nil
-
end
-
2
s12 << r13
-
2
if r13
-
if has_terminal?('\G[uU]', true, index)
-
r14 = true
-
@index += 1
-
else
-
r14 = nil
-
end
-
s12 << r14
-
if r14
-
if has_terminal?('\G[dD]', true, index)
-
r15 = true
-
@index += 1
-
else
-
r15 = nil
-
end
-
s12 << r15
-
if r15
-
if has_terminal?('\G[iI]', true, index)
-
r16 = true
-
@index += 1
-
else
-
r16 = nil
-
end
-
s12 << r16
-
if r16
-
if has_terminal?('\G[oO]', true, index)
-
r17 = true
-
@index += 1
-
else
-
r17 = nil
-
end
-
s12 << r17
-
end
-
end
-
end
-
end
-
2
if s12.last
-
r12 = instantiate_node(SyntaxNode,input, i12...index, s12)
-
r12.extend(DiscreteType2)
-
else
-
2
@index = i12
-
2
r12 = nil
-
end
-
2
if r12
-
r0 = r12
-
else
-
2
i18, s18 = index, []
-
2
if has_terminal?('\G[vV]', true, index)
-
r19 = true
-
@index += 1
-
else
-
2
r19 = nil
-
end
-
2
s18 << r19
-
2
if r19
-
if has_terminal?('\G[iI]', true, index)
-
r20 = true
-
@index += 1
-
else
-
r20 = nil
-
end
-
s18 << r20
-
if r20
-
if has_terminal?('\G[dD]', true, index)
-
r21 = true
-
@index += 1
-
else
-
r21 = nil
-
end
-
s18 << r21
-
if r21
-
if has_terminal?('\G[eE]', true, index)
-
r22 = true
-
@index += 1
-
else
-
r22 = nil
-
end
-
s18 << r22
-
if r22
-
if has_terminal?('\G[oO]', true, index)
-
r23 = true
-
@index += 1
-
else
-
r23 = nil
-
end
-
s18 << r23
-
end
-
end
-
end
-
end
-
2
if s18.last
-
r18 = instantiate_node(SyntaxNode,input, i18...index, s18)
-
r18.extend(DiscreteType3)
-
else
-
2
@index = i18
-
2
r18 = nil
-
end
-
2
if r18
-
r0 = r18
-
else
-
2
i24, s24 = index, []
-
2
if has_terminal?('\G[aA]', true, index)
-
r25 = true
-
@index += 1
-
else
-
2
r25 = nil
-
end
-
2
s24 << r25
-
2
if r25
-
if has_terminal?('\G[pP]', true, index)
-
r26 = true
-
@index += 1
-
else
-
r26 = nil
-
end
-
s24 << r26
-
if r26
-
if has_terminal?('\G[pP]', true, index)
-
r27 = true
-
@index += 1
-
else
-
r27 = nil
-
end
-
s24 << r27
-
if r27
-
if has_terminal?('\G[lL]', true, index)
-
r28 = true
-
@index += 1
-
else
-
r28 = nil
-
end
-
s24 << r28
-
if r28
-
if has_terminal?('\G[iI]', true, index)
-
r29 = true
-
@index += 1
-
else
-
r29 = nil
-
end
-
s24 << r29
-
if r29
-
if has_terminal?('\G[cC]', true, index)
-
r30 = true
-
@index += 1
-
else
-
r30 = nil
-
end
-
s24 << r30
-
if r30
-
if has_terminal?('\G[aA]', true, index)
-
r31 = true
-
@index += 1
-
else
-
r31 = nil
-
end
-
s24 << r31
-
if r31
-
if has_terminal?('\G[tT]', true, index)
-
r32 = true
-
@index += 1
-
else
-
r32 = nil
-
end
-
s24 << r32
-
if r32
-
if has_terminal?('\G[iI]', true, index)
-
r33 = true
-
@index += 1
-
else
-
r33 = nil
-
end
-
s24 << r33
-
if r33
-
if has_terminal?('\G[oO]', true, index)
-
r34 = true
-
@index += 1
-
else
-
r34 = nil
-
end
-
s24 << r34
-
if r34
-
if has_terminal?('\G[nN]', true, index)
-
r35 = true
-
@index += 1
-
else
-
r35 = nil
-
end
-
s24 << r35
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
2
if s24.last
-
r24 = instantiate_node(SyntaxNode,input, i24...index, s24)
-
r24.extend(DiscreteType4)
-
else
-
2
@index = i24
-
2
r24 = nil
-
end
-
2
if r24
-
r0 = r24
-
else
-
2
r36 = _nt_extension_token
-
2
if r36
-
r0 = r36
-
else
-
2
@index = i0
-
2
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
-
6
node_cache[:discrete_type][start_index] = r0
-
-
6
r0
-
end
-
-
1
module CompositeType0
-
end
-
-
1
module CompositeType1
-
end
-
-
1
def _nt_composite_type
-
2
start_index = index
-
2
if node_cache[:composite_type].has_key?(index)
-
cached = node_cache[:composite_type][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
2
i0 = index
-
2
i1, s1 = index, []
-
2
if has_terminal?('\G[mM]', true, index)
-
2
r2 = true
-
2
@index += 1
-
else
-
r2 = nil
-
end
-
2
s1 << r2
-
2
if r2
-
2
if has_terminal?('\G[eE]', true, index)
-
r3 = true
-
@index += 1
-
else
-
2
r3 = nil
-
end
-
2
s1 << r3
-
2
if r3
-
if has_terminal?('\G[sS]', true, index)
-
r4 = true
-
@index += 1
-
else
-
r4 = nil
-
end
-
s1 << r4
-
if r4
-
if has_terminal?('\G[sS]', true, index)
-
r5 = true
-
@index += 1
-
else
-
r5 = nil
-
end
-
s1 << r5
-
if r5
-
if has_terminal?('\G[aA]', true, index)
-
r6 = true
-
@index += 1
-
else
-
r6 = nil
-
end
-
s1 << r6
-
if r6
-
if has_terminal?('\G[gG]', true, index)
-
r7 = true
-
@index += 1
-
else
-
r7 = nil
-
end
-
s1 << r7
-
if r7
-
if has_terminal?('\G[eE]', true, index)
-
r8 = true
-
@index += 1
-
else
-
r8 = nil
-
end
-
s1 << r8
-
end
-
end
-
end
-
end
-
end
-
end
-
2
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(CompositeType0)
-
else
-
2
@index = i1
-
2
r1 = nil
-
end
-
2
if r1
-
r0 = r1
-
else
-
2
i9, s9 = index, []
-
2
if has_terminal?('\G[mM]', true, index)
-
2
r10 = true
-
2
@index += 1
-
else
-
r10 = nil
-
end
-
2
s9 << r10
-
2
if r10
-
2
if has_terminal?('\G[uU]', true, index)
-
2
r11 = true
-
2
@index += 1
-
else
-
r11 = nil
-
end
-
2
s9 << r11
-
2
if r11
-
2
if has_terminal?('\G[lL]', true, index)
-
2
r12 = true
-
2
@index += 1
-
else
-
r12 = nil
-
end
-
2
s9 << r12
-
2
if r12
-
2
if has_terminal?('\G[tT]', true, index)
-
2
r13 = true
-
2
@index += 1
-
else
-
r13 = nil
-
end
-
2
s9 << r13
-
2
if r13
-
2
if has_terminal?('\G[iI]', true, index)
-
2
r14 = true
-
2
@index += 1
-
else
-
r14 = nil
-
end
-
2
s9 << r14
-
2
if r14
-
2
if has_terminal?('\G[pP]', true, index)
-
2
r15 = true
-
2
@index += 1
-
else
-
r15 = nil
-
end
-
2
s9 << r15
-
2
if r15
-
2
if has_terminal?('\G[aA]', true, index)
-
2
r16 = true
-
2
@index += 1
-
else
-
r16 = nil
-
end
-
2
s9 << r16
-
2
if r16
-
2
if has_terminal?('\G[rR]', true, index)
-
2
r17 = true
-
2
@index += 1
-
else
-
r17 = nil
-
end
-
2
s9 << r17
-
2
if r17
-
2
if has_terminal?('\G[tT]', true, index)
-
2
r18 = true
-
2
@index += 1
-
else
-
r18 = nil
-
end
-
2
s9 << r18
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
2
if s9.last
-
2
r9 = instantiate_node(SyntaxNode,input, i9...index, s9)
-
2
r9.extend(CompositeType1)
-
else
-
@index = i9
-
r9 = nil
-
end
-
2
if r9
-
2
r0 = r9
-
else
-
r19 = _nt_extension_token
-
if r19
-
r0 = r19
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
-
2
node_cache[:composite_type][start_index] = r0
-
-
2
r0
-
end
-
-
1
def _nt_extension_token
-
8
start_index = index
-
8
if node_cache[:extension_token].has_key?(index)
-
cached = node_cache[:extension_token][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
8
i0 = index
-
8
r1 = _nt_ietf_token
-
8
if r1
-
r0 = r1
-
else
-
8
r2 = _nt_custom_x_token
-
8
if r2
-
r0 = r2
-
else
-
8
@index = i0
-
8
r0 = nil
-
end
-
end
-
-
8
node_cache[:extension_token][start_index] = r0
-
-
8
r0
-
end
-
-
1
def _nt_sub_type
-
6
start_index = index
-
6
if node_cache[:sub_type].has_key?(index)
-
cached = node_cache[:sub_type][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
6
i0 = index
-
6
r1 = _nt_extension_token
-
6
if r1
-
r0 = r1
-
else
-
6
r2 = _nt_iana_token
-
6
if r2
-
6
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
6
node_cache[:sub_type][start_index] = r0
-
-
6
r0
-
end
-
-
1
module Parameter0
-
1
def attr
-
2
elements[1]
-
end
-
-
1
def val
-
2
elements[3]
-
end
-
-
end
-
-
1
module Parameter1
-
1
def param_hash
-
2
{attr.text_value => val.text_value}
-
end
-
end
-
-
1
def _nt_parameter
-
8
start_index = index
-
8
if node_cache[:parameter].has_key?(index)
-
cached = node_cache[:parameter][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
8
i0, s0 = index, []
-
8
r2 = _nt_CFWS
-
8
if r2
-
8
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
8
s0 << r1
-
8
if r1
-
8
r3 = _nt_attribute
-
8
s0 << r3
-
8
if r3
-
2
if has_terminal?("=", false, index)
-
2
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
2
@index += 1
-
else
-
terminal_parse_failure("=")
-
r4 = nil
-
end
-
2
s0 << r4
-
2
if r4
-
2
r5 = _nt_value
-
2
s0 << r5
-
2
if r5
-
2
r7 = _nt_CFWS
-
2
if r7
-
2
r6 = r7
-
else
-
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
2
s0 << r6
-
end
-
end
-
end
-
end
-
8
if s0.last
-
2
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
2
r0.extend(Parameter0)
-
2
r0.extend(Parameter1)
-
else
-
6
@index = i0
-
6
r0 = nil
-
end
-
-
8
node_cache[:parameter][start_index] = r0
-
-
8
r0
-
end
-
-
1
def _nt_attribute
-
8
start_index = index
-
8
if node_cache[:attribute].has_key?(index)
-
cached = node_cache[:attribute][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
8
s0, i0 = [], index
-
8
loop do
-
23
r1 = _nt_token
-
23
if r1
-
15
s0 << r1
-
else
-
8
break
-
end
-
end
-
8
if s0.empty?
-
6
@index = i0
-
6
r0 = nil
-
else
-
2
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
end
-
-
8
node_cache[:attribute][start_index] = r0
-
-
8
r0
-
end
-
-
1
module Value0
-
1
def text_value
-
1
quoted_content.text_value
-
end
-
end
-
-
1
def _nt_value
-
2
start_index = index
-
2
if node_cache[:value].has_key?(index)
-
cached = node_cache[:value][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
2
i0 = index
-
2
r1 = _nt_quoted_string
-
2
r1.extend(Value0)
-
2
if r1
-
1
r0 = r1
-
else
-
1
s2, i2 = [], index
-
1
loop do
-
6
i3 = index
-
6
r4 = _nt_token
-
6
if r4
-
5
r3 = r4
-
else
-
1
if has_terminal?('\G[\\x3d]', true, index)
-
r5 = true
-
@index += 1
-
else
-
1
r5 = nil
-
end
-
1
if r5
-
r3 = r5
-
else
-
1
@index = i3
-
1
r3 = nil
-
end
-
end
-
6
if r3
-
5
s2 << r3
-
else
-
1
break
-
end
-
end
-
1
if s2.empty?
-
@index = i2
-
r2 = nil
-
else
-
1
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
end
-
1
if r2
-
1
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
2
node_cache[:value][start_index] = r0
-
-
2
r0
-
end
-
-
end
-
-
1
class ContentTypeParser < Treetop::Runtime::CompiledParser
-
1
include ContentType
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module DateTime
-
1
include Treetop::Runtime
-
-
1
def root
-
@root ||= :primary
-
end
-
-
1
include RFC2822
-
-
1
module Primary0
-
1
def day_of_week
-
elements[0]
-
end
-
-
end
-
-
1
module Primary1
-
1
def date
-
elements[1]
-
end
-
-
1
def FWS
-
elements[2]
-
end
-
-
1
def time
-
elements[3]
-
end
-
-
end
-
-
1
def _nt_primary
-
start_index = index
-
if node_cache[:primary].has_key?(index)
-
cached = node_cache[:primary][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
i2, s2 = index, []
-
r3 = _nt_day_of_week
-
s2 << r3
-
if r3
-
if has_terminal?(",", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r4 = nil
-
end
-
s2 << r4
-
end
-
if s2.last
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
r2.extend(Primary0)
-
else
-
@index = i2
-
r2 = nil
-
end
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
r5 = _nt_date
-
s0 << r5
-
if r5
-
r6 = _nt_FWS
-
s0 << r6
-
if r6
-
r7 = _nt_time
-
s0 << r7
-
if r7
-
r9 = _nt_CFWS
-
if r9
-
r8 = r9
-
else
-
r8 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r8
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Primary1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:primary][start_index] = r0
-
-
r0
-
end
-
-
end
-
-
1
class DateTimeParser < Treetop::Runtime::CompiledParser
-
1
include DateTime
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module EnvelopeFrom
-
1
include Treetop::Runtime
-
-
1
def root
-
@root ||= :primary
-
end
-
-
1
include RFC2822
-
-
1
module Primary0
-
1
def addr_spec
-
elements[0]
-
end
-
-
1
def ctime_date
-
elements[1]
-
end
-
end
-
-
1
def _nt_primary
-
start_index = index
-
if node_cache[:primary].has_key?(index)
-
cached = node_cache[:primary][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_addr_spec
-
s0 << r1
-
if r1
-
r2 = _nt_ctime_date
-
s0 << r2
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Primary0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:primary][start_index] = r0
-
-
r0
-
end
-
-
1
module CtimeDate0
-
1
def day_name
-
elements[0]
-
end
-
-
1
def month_name
-
elements[2]
-
end
-
-
1
def day
-
elements[4]
-
end
-
-
1
def time_of_day
-
elements[6]
-
end
-
-
1
def year
-
elements[8]
-
end
-
end
-
-
1
def _nt_ctime_date
-
start_index = index
-
if node_cache[:ctime_date].has_key?(index)
-
cached = node_cache[:ctime_date][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_day_name
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
if has_terminal?(" ", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(" ")
-
r3 = nil
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
if s2.empty?
-
@index = i2
-
r2 = nil
-
else
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
end
-
s0 << r2
-
if r2
-
r4 = _nt_month_name
-
s0 << r4
-
if r4
-
s5, i5 = [], index
-
loop do
-
if has_terminal?(" ", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(" ")
-
r6 = nil
-
end
-
if r6
-
s5 << r6
-
else
-
break
-
end
-
end
-
if s5.empty?
-
@index = i5
-
r5 = nil
-
else
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
end
-
s0 << r5
-
if r5
-
r7 = _nt_day
-
s0 << r7
-
if r7
-
if has_terminal?(" ", false, index)
-
r8 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(" ")
-
r8 = nil
-
end
-
s0 << r8
-
if r8
-
r9 = _nt_time_of_day
-
s0 << r9
-
if r9
-
if has_terminal?(" ", false, index)
-
r10 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(" ")
-
r10 = nil
-
end
-
s0 << r10
-
if r10
-
r11 = _nt_year
-
s0 << r11
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(CtimeDate0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:ctime_date][start_index] = r0
-
-
r0
-
end
-
-
end
-
-
1
class EnvelopeFromParser < Treetop::Runtime::CompiledParser
-
1
include EnvelopeFrom
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module MessageIds
-
1
include Treetop::Runtime
-
-
1
def root
-
4
@root ||= :primary
-
end
-
-
1
include RFC2822
-
-
1
module Primary0
-
1
def message_ids
-
4
[first_msg_id] + other_msg_ids.elements.map { |o| o.msg_id_value }
-
end
-
end
-
-
1
def _nt_primary
-
4
start_index = index
-
4
if node_cache[:primary].has_key?(index)
-
cached = node_cache[:primary][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
r0 = _nt_message_ids
-
4
r0.extend(Primary0)
-
-
4
node_cache[:primary][start_index] = r0
-
-
4
r0
-
end
-
-
end
-
-
1
class MessageIdsParser < Treetop::Runtime::CompiledParser
-
1
include MessageIds
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module MimeVersion
-
1
include Treetop::Runtime
-
-
1
def root
-
4
@root ||= :version
-
end
-
-
1
include RFC2822
-
-
1
module Version0
-
1
def CFWS1
-
elements[0]
-
end
-
-
1
def major_digits
-
4
elements[1]
-
end
-
-
1
def minor_digits
-
4
elements[5]
-
end
-
-
1
def CFWS2
-
elements[6]
-
end
-
end
-
-
1
module Version1
-
1
def major
-
4
major_digits
-
end
-
-
1
def minor
-
4
minor_digits
-
end
-
end
-
-
1
def _nt_version
-
4
start_index = index
-
4
if node_cache[:version].has_key?(index)
-
cached = node_cache[:version][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0, s0 = index, []
-
4
r1 = _nt_CFWS
-
4
s0 << r1
-
4
if r1
-
4
s2, i2 = [], index
-
4
loop do
-
8
r3 = _nt_DIGIT
-
8
if r3
-
4
s2 << r3
-
else
-
4
break
-
end
-
end
-
4
if s2.empty?
-
@index = i2
-
r2 = nil
-
else
-
4
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
end
-
4
s0 << r2
-
4
if r2
-
4
r5 = _nt_comment
-
4
if r5
-
r4 = r5
-
else
-
4
r4 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
4
s0 << r4
-
4
if r4
-
4
if has_terminal?(".", false, index)
-
4
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
4
@index += 1
-
else
-
terminal_parse_failure(".")
-
r6 = nil
-
end
-
4
s0 << r6
-
4
if r6
-
4
r8 = _nt_comment
-
4
if r8
-
r7 = r8
-
else
-
4
r7 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
4
s0 << r7
-
4
if r7
-
4
s9, i9 = [], index
-
4
loop do
-
8
r10 = _nt_DIGIT
-
8
if r10
-
4
s9 << r10
-
else
-
4
break
-
end
-
end
-
4
if s9.empty?
-
@index = i9
-
r9 = nil
-
else
-
4
r9 = instantiate_node(SyntaxNode,input, i9...index, s9)
-
end
-
4
s0 << r9
-
4
if r9
-
4
r11 = _nt_CFWS
-
4
s0 << r11
-
end
-
end
-
end
-
end
-
end
-
end
-
4
if s0.last
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
4
r0.extend(Version0)
-
4
r0.extend(Version1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
4
node_cache[:version][start_index] = r0
-
-
4
r0
-
end
-
-
end
-
-
1
class MimeVersionParser < Treetop::Runtime::CompiledParser
-
1
include MimeVersion
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module PhraseLists
-
1
include Treetop::Runtime
-
-
1
def root
-
@root ||= :primary_phrase
-
end
-
-
1
include RFC2822
-
-
1
module PrimaryPhrase0
-
1
def phrases
-
[first_phrase] + other_phrases.elements.map { |o| o.phrase_value }
-
end
-
end
-
-
1
def _nt_primary_phrase
-
start_index = index
-
if node_cache[:primary_phrase].has_key?(index)
-
cached = node_cache[:primary_phrase][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
r0 = _nt_phrase_list
-
r0.extend(PrimaryPhrase0)
-
-
node_cache[:primary_phrase][start_index] = r0
-
-
r0
-
end
-
-
end
-
-
1
class PhraseListsParser < Treetop::Runtime::CompiledParser
-
1
include PhraseLists
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module Received
-
1
include Treetop::Runtime
-
-
1
def root
-
@root ||= :primary
-
end
-
-
1
include RFC2822
-
-
1
module Primary0
-
1
def name_val_list
-
elements[0]
-
end
-
-
1
def date_time
-
elements[2]
-
end
-
end
-
-
1
def _nt_primary
-
start_index = index
-
if node_cache[:primary].has_key?(index)
-
cached = node_cache[:primary][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_name_val_list
-
s0 << r1
-
if r1
-
if has_terminal?(";", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(";")
-
r2 = nil
-
end
-
s0 << r2
-
if r2
-
r3 = _nt_date_time
-
s0 << r3
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Primary0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:primary][start_index] = r0
-
-
r0
-
end
-
-
end
-
-
1
class ReceivedParser < Treetop::Runtime::CompiledParser
-
1
include Received
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module RFC2045
-
1
include Treetop::Runtime
-
-
1
def root
-
@root ||= :tspecials
-
end
-
-
1
def _nt_tspecials
-
start_index = index
-
if node_cache[:tspecials].has_key?(index)
-
cached = node_cache[:tspecials][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
if has_terminal?("(", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("(")
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
if has_terminal?(")", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(")")
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?("<", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("<")
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
if has_terminal?(">", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(">")
-
r4 = nil
-
end
-
if r4
-
r0 = r4
-
else
-
if has_terminal?("@", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("@")
-
r5 = nil
-
end
-
if r5
-
r0 = r5
-
else
-
if has_terminal?(",", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r6 = nil
-
end
-
if r6
-
r0 = r6
-
else
-
if has_terminal?(";", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(";")
-
r7 = nil
-
end
-
if r7
-
r0 = r7
-
else
-
if has_terminal?(":", false, index)
-
r8 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r8 = nil
-
end
-
if r8
-
r0 = r8
-
else
-
if has_terminal?('\\', false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure('\\')
-
r9 = nil
-
end
-
if r9
-
r0 = r9
-
else
-
if has_terminal?("<", false, index)
-
r10 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("<")
-
r10 = nil
-
end
-
if r10
-
r0 = r10
-
else
-
if has_terminal?(">", false, index)
-
r11 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(">")
-
r11 = nil
-
end
-
if r11
-
r0 = r11
-
else
-
if has_terminal?("/", false, index)
-
r12 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("/")
-
r12 = nil
-
end
-
if r12
-
r0 = r12
-
else
-
if has_terminal?("[", false, index)
-
r13 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("[")
-
r13 = nil
-
end
-
if r13
-
r0 = r13
-
else
-
if has_terminal?("]", false, index)
-
r14 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("]")
-
r14 = nil
-
end
-
if r14
-
r0 = r14
-
else
-
if has_terminal?("?", false, index)
-
r15 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("?")
-
r15 = nil
-
end
-
if r15
-
r0 = r15
-
else
-
if has_terminal?("=", false, index)
-
r16 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("=")
-
r16 = nil
-
end
-
if r16
-
r0 = r16
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
-
node_cache[:tspecials][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_ietf_token
-
14
start_index = index
-
14
if node_cache[:ietf_token].has_key?(index)
-
cached = node_cache[:ietf_token][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
14
i0 = index
-
14
if has_terminal?("7bit", false, index)
-
6
r1 = instantiate_node(SyntaxNode,input, index...(index + 4))
-
6
@index += 4
-
else
-
8
terminal_parse_failure("7bit")
-
8
r1 = nil
-
end
-
14
if r1
-
6
r0 = r1
-
else
-
8
if has_terminal?("8bit", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 4))
-
@index += 4
-
else
-
8
terminal_parse_failure("8bit")
-
8
r2 = nil
-
end
-
8
if r2
-
r0 = r2
-
else
-
8
if has_terminal?("binary", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 6))
-
@index += 6
-
else
-
8
terminal_parse_failure("binary")
-
8
r3 = nil
-
end
-
8
if r3
-
r0 = r3
-
else
-
8
if has_terminal?("quoted-printable", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 16))
-
@index += 16
-
else
-
8
terminal_parse_failure("quoted-printable")
-
8
r4 = nil
-
end
-
8
if r4
-
r0 = r4
-
else
-
8
if has_terminal?("base64", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 6))
-
@index += 6
-
else
-
8
terminal_parse_failure("base64")
-
8
r5 = nil
-
end
-
8
if r5
-
r0 = r5
-
else
-
8
@index = i0
-
8
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
-
14
node_cache[:ietf_token][start_index] = r0
-
-
14
r0
-
end
-
-
1
module CustomXToken0
-
end
-
-
1
def _nt_custom_x_token
-
8
start_index = index
-
8
if node_cache[:custom_x_token].has_key?(index)
-
cached = node_cache[:custom_x_token][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
8
i0, s0 = index, []
-
8
if has_terminal?('\G[xX]', true, index)
-
r1 = true
-
@index += 1
-
else
-
8
r1 = nil
-
end
-
8
s0 << r1
-
8
if r1
-
if has_terminal?("-", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("-")
-
r2 = nil
-
end
-
s0 << r2
-
if r2
-
s3, i3 = [], index
-
loop do
-
r4 = _nt_token
-
if r4
-
s3 << r4
-
else
-
break
-
end
-
end
-
if s3.empty?
-
@index = i3
-
r3 = nil
-
else
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
end
-
s0 << r3
-
end
-
end
-
8
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(CustomXToken0)
-
else
-
8
@index = i0
-
8
r0 = nil
-
end
-
-
8
node_cache[:custom_x_token][start_index] = r0
-
-
8
r0
-
end
-
-
1
def _nt_iana_token
-
6
start_index = index
-
6
if node_cache[:iana_token].has_key?(index)
-
cached = node_cache[:iana_token][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
6
s0, i0 = [], index
-
6
loop do
-
40
r1 = _nt_token
-
40
if r1
-
34
s0 << r1
-
else
-
6
break
-
end
-
end
-
6
if s0.empty?
-
@index = i0
-
r0 = nil
-
else
-
6
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
end
-
-
6
node_cache[:iana_token][start_index] = r0
-
-
6
r0
-
end
-
-
1
def _nt_token
-
69
start_index = index
-
69
if node_cache[:token].has_key?(index)
-
6
cached = node_cache[:token][index]
-
6
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
6
return cached
-
end
-
-
63
i0 = index
-
63
if has_terminal?('\G[\\x21-\\x27]', true, index)
-
r1 = true
-
@index += 1
-
else
-
63
r1 = nil
-
end
-
63
if r1
-
r0 = r1
-
else
-
63
if has_terminal?('\G[\\x2a-\\x2b]', true, index)
-
r2 = true
-
@index += 1
-
else
-
63
r2 = nil
-
end
-
63
if r2
-
r0 = r2
-
else
-
63
if has_terminal?('\G[\\x2c-\\x2e]', true, index)
-
1
r3 = true
-
1
@index += 1
-
else
-
62
r3 = nil
-
end
-
63
if r3
-
1
r0 = r3
-
else
-
62
if has_terminal?('\G[\\x30-\\x39]', true, index)
-
1
r4 = true
-
1
@index += 1
-
else
-
61
r4 = nil
-
end
-
62
if r4
-
1
r0 = r4
-
else
-
61
if has_terminal?('\G[\\x41-\\x5a]', true, index)
-
3
r5 = true
-
3
@index += 1
-
else
-
58
r5 = nil
-
end
-
61
if r5
-
3
r0 = r5
-
else
-
58
if has_terminal?('\G[\\x5e-\\x7e]', true, index)
-
49
r6 = true
-
49
@index += 1
-
else
-
9
r6 = nil
-
end
-
58
if r6
-
49
r0 = r6
-
else
-
9
@index = i0
-
9
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
-
63
node_cache[:token][start_index] = r0
-
-
63
r0
-
end
-
-
end
-
-
1
class RFC2045Parser < Treetop::Runtime::CompiledParser
-
1
include RFC2045
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module RFC2822
-
1
include Treetop::Runtime
-
-
1
def root
-
@root ||= :ALPHA
-
end
-
-
1
include RFC2822Obsolete
-
-
1
def _nt_ALPHA
-
267
start_index = index
-
267
if node_cache[:ALPHA].has_key?(index)
-
cached = node_cache[:ALPHA][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
267
if has_terminal?('\G[a-zA-Z]', true, index)
-
144
r0 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
144
@index += 1
-
else
-
123
r0 = nil
-
end
-
-
267
node_cache[:ALPHA][start_index] = r0
-
-
267
r0
-
end
-
-
1
def _nt_DIGIT
-
139
start_index = index
-
139
if node_cache[:DIGIT].has_key?(index)
-
cached = node_cache[:DIGIT][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
139
if has_terminal?('\G[0-9]', true, index)
-
91
r0 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
91
@index += 1
-
else
-
48
r0 = nil
-
end
-
-
139
node_cache[:DIGIT][start_index] = r0
-
-
139
r0
-
end
-
-
1
def _nt_DQUOTE
-
43
start_index = index
-
43
if node_cache[:DQUOTE].has_key?(index)
-
4
cached = node_cache[:DQUOTE][index]
-
4
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
4
return cached
-
end
-
-
39
if has_terminal?('"', false, index)
-
2
r0 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
2
@index += 1
-
else
-
37
terminal_parse_failure('"')
-
37
r0 = nil
-
end
-
-
39
node_cache[:DQUOTE][start_index] = r0
-
-
39
r0
-
end
-
-
1
def _nt_LF
-
start_index = index
-
if node_cache[:LF].has_key?(index)
-
cached = node_cache[:LF][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
if has_terminal?("\n", false, index)
-
r0 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("\n")
-
r0 = nil
-
end
-
-
node_cache[:LF][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_CR
-
start_index = index
-
if node_cache[:CR].has_key?(index)
-
cached = node_cache[:CR][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
if has_terminal?("\r", false, index)
-
r0 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("\r")
-
r0 = nil
-
end
-
-
node_cache[:CR][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_CRLF
-
232
start_index = index
-
232
if node_cache[:CRLF].has_key?(index)
-
118
cached = node_cache[:CRLF][index]
-
118
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
118
return cached
-
end
-
-
114
if has_terminal?("\r\n", false, index)
-
r0 = instantiate_node(SyntaxNode,input, index...(index + 2))
-
@index += 2
-
else
-
114
terminal_parse_failure("\r\n")
-
114
r0 = nil
-
end
-
-
114
node_cache[:CRLF][start_index] = r0
-
-
114
r0
-
end
-
-
1
def _nt_WSP
-
236
start_index = index
-
236
if node_cache[:WSP].has_key?(index)
-
122
cached = node_cache[:WSP][index]
-
122
if cached
-
4
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
4
@index = cached.interval.end
-
end
-
122
return cached
-
end
-
-
114
if has_terminal?('\G[\\x09\\x20]', true, index)
-
4
r0 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
4
@index += 1
-
else
-
110
r0 = nil
-
end
-
-
114
node_cache[:WSP][start_index] = r0
-
-
114
r0
-
end
-
-
1
module FWS0
-
1
def CRLF
-
elements[1]
-
end
-
-
end
-
-
1
module FWS1
-
1
def CRLF
-
elements[0]
-
end
-
-
end
-
-
1
def _nt_FWS
-
189
start_index = index
-
189
if node_cache[:FWS].has_key?(index)
-
75
cached = node_cache[:FWS][index]
-
75
if cached
-
4
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
4
@index = cached.interval.end
-
end
-
75
return cached
-
end
-
-
114
i0 = index
-
114
i1, s1 = index, []
-
114
s2, i2 = [], index
-
114
loop do
-
118
r3 = _nt_WSP
-
118
if r3
-
4
s2 << r3
-
else
-
114
break
-
end
-
end
-
114
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
114
s1 << r2
-
114
if r2
-
114
r4 = _nt_CRLF
-
114
s1 << r4
-
114
if r4
-
s5, i5 = [], index
-
loop do
-
r6 = _nt_WSP
-
if r6
-
s5 << r6
-
else
-
break
-
end
-
end
-
if s5.empty?
-
@index = i5
-
r5 = nil
-
else
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
end
-
s1 << r5
-
end
-
end
-
114
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(FWS0)
-
else
-
114
@index = i1
-
114
r1 = nil
-
end
-
114
if r1
-
r0 = r1
-
else
-
114
i7, s7 = index, []
-
114
r8 = _nt_CRLF
-
114
s7 << r8
-
114
if r8
-
s9, i9 = [], index
-
loop do
-
r10 = _nt_WSP
-
if r10
-
s9 << r10
-
else
-
break
-
end
-
end
-
if s9.empty?
-
@index = i9
-
r9 = nil
-
else
-
r9 = instantiate_node(SyntaxNode,input, i9...index, s9)
-
end
-
s7 << r9
-
end
-
114
if s7.last
-
r7 = instantiate_node(SyntaxNode,input, i7...index, s7)
-
r7.extend(FWS1)
-
else
-
114
@index = i7
-
114
r7 = nil
-
end
-
114
if r7
-
r0 = r7
-
else
-
114
r11 = _nt_obs_FWS
-
114
if r11
-
4
r0 = r11
-
else
-
110
@index = i0
-
110
r0 = nil
-
end
-
end
-
end
-
-
114
node_cache[:FWS][start_index] = r0
-
-
114
r0
-
end
-
-
1
module CFWS0
-
1
def comment
-
elements[1]
-
end
-
end
-
-
1
module CFWS1
-
end
-
-
1
def _nt_CFWS
-
125
start_index = index
-
125
if node_cache[:CFWS].has_key?(index)
-
57
cached = node_cache[:CFWS][index]
-
57
if cached
-
57
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
57
@index = cached.interval.end
-
end
-
57
return cached
-
end
-
-
68
i0, s0 = index, []
-
68
s1, i1 = [], index
-
68
loop do
-
68
i2, s2 = index, []
-
68
s3, i3 = [], index
-
68
loop do
-
72
r4 = _nt_FWS
-
72
if r4
-
4
s3 << r4
-
else
-
68
break
-
end
-
end
-
68
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
68
s2 << r3
-
68
if r3
-
68
r5 = _nt_comment
-
68
s2 << r5
-
end
-
68
if s2.last
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
r2.extend(CFWS0)
-
else
-
68
@index = i2
-
68
r2 = nil
-
end
-
68
if r2
-
s1 << r2
-
else
-
68
break
-
end
-
end
-
68
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
68
s0 << r1
-
68
if r1
-
68
r7 = _nt_FWS
-
68
if r7
-
4
r6 = r7
-
else
-
64
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
68
s0 << r6
-
end
-
68
if s0.last
-
68
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
68
r0.extend(CFWS1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
68
node_cache[:CFWS][start_index] = r0
-
-
68
r0
-
end
-
-
1
def _nt_NO_WS_CTL
-
44
start_index = index
-
44
if node_cache[:NO_WS_CTL].has_key?(index)
-
cached = node_cache[:NO_WS_CTL][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
44
i0 = index
-
44
if has_terminal?('\G[\\x01-\\x08]', true, index)
-
r1 = true
-
@index += 1
-
else
-
44
r1 = nil
-
end
-
44
if r1
-
r0 = r1
-
else
-
44
if has_terminal?('\G[\\x0B-\\x0C]', true, index)
-
r2 = true
-
@index += 1
-
else
-
44
r2 = nil
-
end
-
44
if r2
-
r0 = r2
-
else
-
44
if has_terminal?('\G[\\x0E-\\x1F]', true, index)
-
r3 = true
-
@index += 1
-
else
-
44
r3 = nil
-
end
-
44
if r3
-
r0 = r3
-
else
-
44
if has_terminal?('\G[\\x7f]', true, index)
-
r4 = true
-
@index += 1
-
else
-
44
r4 = nil
-
end
-
44
if r4
-
r0 = r4
-
else
-
44
@index = i0
-
44
r0 = nil
-
end
-
end
-
end
-
end
-
-
44
node_cache[:NO_WS_CTL][start_index] = r0
-
-
44
r0
-
end
-
-
1
def _nt_specials
-
start_index = index
-
if node_cache[:specials].has_key?(index)
-
cached = node_cache[:specials][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
if has_terminal?("(", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("(")
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
if has_terminal?(")", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(")")
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?("<", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("<")
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
if has_terminal?(">", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(">")
-
r4 = nil
-
end
-
if r4
-
r0 = r4
-
else
-
if has_terminal?("[", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("[")
-
r5 = nil
-
end
-
if r5
-
r0 = r5
-
else
-
if has_terminal?("]", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("]")
-
r6 = nil
-
end
-
if r6
-
r0 = r6
-
else
-
if has_terminal?(":", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r7 = nil
-
end
-
if r7
-
r0 = r7
-
else
-
if has_terminal?(";", false, index)
-
r8 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(";")
-
r8 = nil
-
end
-
if r8
-
r0 = r8
-
else
-
if has_terminal?("@", false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("@")
-
r9 = nil
-
end
-
if r9
-
r0 = r9
-
else
-
if has_terminal?('\\', false, index)
-
r10 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure('\\')
-
r10 = nil
-
end
-
if r10
-
r0 = r10
-
else
-
if has_terminal?(",", false, index)
-
r11 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r11 = nil
-
end
-
if r11
-
r0 = r11
-
else
-
if has_terminal?(".", false, index)
-
r12 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(".")
-
r12 = nil
-
end
-
if r12
-
r0 = r12
-
else
-
r13 = _nt_DQUOTE
-
if r13
-
r0 = r13
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
-
node_cache[:specials][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_ctext
-
start_index = index
-
if node_cache[:ctext].has_key?(index)
-
cached = node_cache[:ctext][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_NO_WS_CTL
-
if r1
-
r0 = r1
-
else
-
if has_terminal?('\G[\\x21-\\x27]', true, index)
-
r2 = true
-
@index += 1
-
else
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?('\G[\\x2a-\\x5b]', true, index)
-
r3 = true
-
@index += 1
-
else
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
if has_terminal?('\G[\\x5d-\\x7e]', true, index)
-
r4 = true
-
@index += 1
-
else
-
r4 = nil
-
end
-
if r4
-
r0 = r4
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
-
node_cache[:ctext][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_ccontent
-
start_index = index
-
if node_cache[:ccontent].has_key?(index)
-
cached = node_cache[:ccontent][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_ctext
-
if r1
-
r0 = r1
-
else
-
r2 = _nt_quoted_pair
-
if r2
-
r0 = r2
-
else
-
r3 = _nt_comment
-
if r3
-
r0 = r3
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
-
node_cache[:ccontent][start_index] = r0
-
-
r0
-
end
-
-
1
module Comment0
-
1
def ccontent
-
elements[1]
-
end
-
end
-
-
1
module Comment1
-
end
-
-
1
def _nt_comment
-
76
start_index = index
-
76
if node_cache[:comment].has_key?(index)
-
2
cached = node_cache[:comment][index]
-
2
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
2
return cached
-
end
-
-
74
i0, s0 = index, []
-
74
if has_terminal?("(", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
74
terminal_parse_failure("(")
-
74
r1 = nil
-
end
-
74
s0 << r1
-
74
if r1
-
s2, i2 = [], index
-
loop do
-
i3, s3 = index, []
-
r5 = _nt_FWS
-
if r5
-
r4 = r5
-
else
-
r4 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s3 << r4
-
if r4
-
r6 = _nt_ccontent
-
s3 << r6
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(Comment0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
r8 = _nt_FWS
-
if r8
-
r7 = r8
-
else
-
r7 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r7
-
if r7
-
if has_terminal?(")", false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(")")
-
r9 = nil
-
end
-
s0 << r9
-
end
-
end
-
end
-
74
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Comment1)
-
else
-
74
@index = i0
-
74
r0 = nil
-
end
-
-
74
node_cache[:comment][start_index] = r0
-
-
74
r0
-
end
-
-
1
def _nt_atext
-
239
start_index = index
-
239
if node_cache[:atext].has_key?(index)
-
48
cached = node_cache[:atext][index]
-
48
if cached
-
24
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
24
@index = cached.interval.end
-
end
-
48
return cached
-
end
-
-
191
i0 = index
-
191
r1 = _nt_ALPHA
-
191
if r1
-
84
r0 = r1
-
else
-
107
r2 = _nt_DIGIT
-
107
if r2
-
83
r0 = r2
-
else
-
24
if has_terminal?("!", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("!")
-
24
r3 = nil
-
end
-
24
if r3
-
r0 = r3
-
else
-
24
if has_terminal?("#", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("#")
-
24
r4 = nil
-
end
-
24
if r4
-
r0 = r4
-
else
-
24
if has_terminal?("$", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("$")
-
24
r5 = nil
-
end
-
24
if r5
-
r0 = r5
-
else
-
24
if has_terminal?("%", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("%")
-
24
r6 = nil
-
end
-
24
if r6
-
r0 = r6
-
else
-
24
if has_terminal?("&", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("&")
-
24
r7 = nil
-
end
-
24
if r7
-
r0 = r7
-
else
-
24
if has_terminal?("'", false, index)
-
r8 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("'")
-
24
r8 = nil
-
end
-
24
if r8
-
r0 = r8
-
else
-
24
if has_terminal?("*", false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("*")
-
24
r9 = nil
-
end
-
24
if r9
-
r0 = r9
-
else
-
24
if has_terminal?("+", false, index)
-
r10 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("+")
-
24
r10 = nil
-
end
-
24
if r10
-
r0 = r10
-
else
-
24
if has_terminal?("-", false, index)
-
r11 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("-")
-
24
r11 = nil
-
end
-
24
if r11
-
r0 = r11
-
else
-
24
if has_terminal?("/", false, index)
-
r12 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("/")
-
24
r12 = nil
-
end
-
24
if r12
-
r0 = r12
-
else
-
24
if has_terminal?("=", false, index)
-
r13 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("=")
-
24
r13 = nil
-
end
-
24
if r13
-
r0 = r13
-
else
-
24
if has_terminal?("?", false, index)
-
r14 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("?")
-
24
r14 = nil
-
end
-
24
if r14
-
r0 = r14
-
else
-
24
if has_terminal?("^", false, index)
-
r15 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
24
terminal_parse_failure("^")
-
24
r15 = nil
-
end
-
24
if r15
-
r0 = r15
-
else
-
24
if has_terminal?("_", false, index)
-
4
r16 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
4
@index += 1
-
else
-
20
terminal_parse_failure("_")
-
20
r16 = nil
-
end
-
24
if r16
-
4
r0 = r16
-
else
-
20
if has_terminal?("`", false, index)
-
r17 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
20
terminal_parse_failure("`")
-
20
r17 = nil
-
end
-
20
if r17
-
r0 = r17
-
else
-
20
if has_terminal?("{", false, index)
-
r18 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
20
terminal_parse_failure("{")
-
20
r18 = nil
-
end
-
20
if r18
-
r0 = r18
-
else
-
20
if has_terminal?("|", false, index)
-
r19 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
20
terminal_parse_failure("|")
-
20
r19 = nil
-
end
-
20
if r19
-
r0 = r19
-
else
-
20
if has_terminal?("}", false, index)
-
r20 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
20
terminal_parse_failure("}")
-
20
r20 = nil
-
end
-
20
if r20
-
r0 = r20
-
else
-
20
if has_terminal?("~", false, index)
-
r21 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
20
terminal_parse_failure("~")
-
20
r21 = nil
-
end
-
20
if r21
-
r0 = r21
-
else
-
20
@index = i0
-
20
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
-
191
node_cache[:atext][start_index] = r0
-
-
191
r0
-
end
-
-
1
def _nt_mtext
-
8
start_index = index
-
8
if node_cache[:mtext].has_key?(index)
-
cached = node_cache[:mtext][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
8
s0, i0 = [], index
-
8
loop do
-
123
i1 = index
-
123
r2 = _nt_atext
-
123
if r2
-
115
r1 = r2
-
else
-
8
if has_terminal?(".", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure(".")
-
8
r3 = nil
-
end
-
8
if r3
-
r1 = r3
-
else
-
8
@index = i1
-
8
r1 = nil
-
end
-
end
-
123
if r1
-
115
s0 << r1
-
else
-
8
break
-
end
-
end
-
8
if s0.empty?
-
4
@index = i0
-
4
r0 = nil
-
else
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
end
-
-
8
node_cache[:mtext][start_index] = r0
-
-
8
r0
-
end
-
-
1
module Atom0
-
end
-
-
1
def _nt_atom
-
16
start_index = index
-
16
if node_cache[:atom].has_key?(index)
-
cached = node_cache[:atom][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
16
i0, s0 = index, []
-
16
r2 = _nt_CFWS
-
16
if r2
-
16
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
16
s0 << r1
-
16
if r1
-
16
s3, i3 = [], index
-
16
loop do
-
48
r4 = _nt_atext
-
48
if r4
-
32
s3 << r4
-
else
-
16
break
-
end
-
end
-
16
if s3.empty?
-
8
@index = i3
-
8
r3 = nil
-
else
-
8
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
end
-
16
s0 << r3
-
16
if r3
-
8
r6 = _nt_CFWS
-
8
if r6
-
8
r5 = r6
-
else
-
r5 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
8
s0 << r5
-
end
-
end
-
16
if s0.last
-
8
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
8
r0.extend(Atom0)
-
else
-
8
@index = i0
-
8
r0 = nil
-
end
-
-
16
node_cache[:atom][start_index] = r0
-
-
16
r0
-
end
-
-
1
module DotAtom0
-
1
def dot_atom_text
-
elements[1]
-
end
-
-
end
-
-
1
def _nt_dot_atom
-
4
start_index = index
-
4
if node_cache[:dot_atom].has_key?(index)
-
cached = node_cache[:dot_atom][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0, s0 = index, []
-
4
r2 = _nt_CFWS
-
4
if r2
-
4
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
4
s0 << r1
-
4
if r1
-
4
r3 = _nt_dot_atom_text
-
4
s0 << r3
-
4
if r3
-
4
r5 = _nt_CFWS
-
4
if r5
-
4
r4 = r5
-
else
-
r4 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
4
s0 << r4
-
end
-
end
-
4
if s0.last
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
4
r0.extend(DotAtom0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
4
node_cache[:dot_atom][start_index] = r0
-
-
4
r0
-
end
-
-
1
module LocalDotAtom0
-
1
def local_dot_atom_text
-
elements[1]
-
end
-
-
end
-
-
1
def _nt_local_dot_atom
-
4
start_index = index
-
4
if node_cache[:local_dot_atom].has_key?(index)
-
cached = node_cache[:local_dot_atom][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0, s0 = index, []
-
4
r2 = _nt_CFWS
-
4
if r2
-
4
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
4
s0 << r1
-
4
if r1
-
4
r3 = _nt_local_dot_atom_text
-
4
s0 << r3
-
4
if r3
-
4
r5 = _nt_CFWS
-
4
if r5
-
4
r4 = r5
-
else
-
r4 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
4
s0 << r4
-
end
-
end
-
4
if s0.last
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
4
r0.extend(LocalDotAtom0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
4
node_cache[:local_dot_atom][start_index] = r0
-
-
4
r0
-
end
-
-
1
def _nt_message_id_text
-
4
start_index = index
-
4
if node_cache[:message_id_text].has_key?(index)
-
cached = node_cache[:message_id_text][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
s0, i0 = [], index
-
4
loop do
-
8
r1 = _nt_mtext
-
8
if r1
-
4
s0 << r1
-
else
-
4
break
-
end
-
end
-
4
if s0.empty?
-
@index = i0
-
r0 = nil
-
else
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
end
-
-
4
node_cache[:message_id_text][start_index] = r0
-
-
4
r0
-
end
-
-
1
module DotAtomText0
-
1
def domain_text
-
elements[0]
-
end
-
-
end
-
-
1
def _nt_dot_atom_text
-
4
start_index = index
-
4
if node_cache[:dot_atom_text].has_key?(index)
-
cached = node_cache[:dot_atom_text][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
s0, i0 = [], index
-
4
loop do
-
12
i1, s1 = index, []
-
12
r2 = _nt_domain_text
-
12
s1 << r2
-
12
if r2
-
8
if has_terminal?(".", false, index)
-
4
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
4
@index += 1
-
else
-
4
terminal_parse_failure(".")
-
4
r4 = nil
-
end
-
8
if r4
-
4
r3 = r4
-
else
-
4
r3 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
8
s1 << r3
-
end
-
12
if s1.last
-
8
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
8
r1.extend(DotAtomText0)
-
else
-
4
@index = i1
-
4
r1 = nil
-
end
-
12
if r1
-
8
s0 << r1
-
else
-
4
break
-
end
-
end
-
4
if s0.empty?
-
@index = i0
-
r0 = nil
-
else
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
end
-
-
4
node_cache[:dot_atom_text][start_index] = r0
-
-
4
r0
-
end
-
-
1
module LocalDotAtomText0
-
1
def domain_text
-
elements[1]
-
end
-
end
-
-
1
def _nt_local_dot_atom_text
-
4
start_index = index
-
4
if node_cache[:local_dot_atom_text].has_key?(index)
-
cached = node_cache[:local_dot_atom_text][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
s0, i0 = [], index
-
4
loop do
-
8
i1, s1 = index, []
-
8
s2, i2 = [], index
-
8
loop do
-
8
if has_terminal?(".", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure(".")
-
8
r3 = nil
-
end
-
8
if r3
-
s2 << r3
-
else
-
8
break
-
end
-
end
-
8
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
8
s1 << r2
-
8
if r2
-
8
r4 = _nt_domain_text
-
8
s1 << r4
-
end
-
8
if s1.last
-
4
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
4
r1.extend(LocalDotAtomText0)
-
else
-
4
@index = i1
-
4
r1 = nil
-
end
-
8
if r1
-
4
s0 << r1
-
else
-
4
break
-
end
-
end
-
4
if s0.empty?
-
@index = i0
-
r0 = nil
-
else
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
end
-
-
4
node_cache[:local_dot_atom_text][start_index] = r0
-
-
4
r0
-
end
-
-
1
module DomainText0
-
1
def quoted_domain
-
elements[1]
-
end
-
end
-
-
1
module DomainText1
-
1
def DQUOTE1
-
elements[0]
-
end
-
-
1
def DQUOTE2
-
elements[3]
-
end
-
end
-
-
1
def _nt_domain_text
-
20
start_index = index
-
20
if node_cache[:domain_text].has_key?(index)
-
cached = node_cache[:domain_text][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
20
i0 = index
-
20
i1, s1 = index, []
-
20
r2 = _nt_DQUOTE
-
20
s1 << r2
-
20
if r2
-
s3, i3 = [], index
-
loop do
-
i4, s4 = index, []
-
r6 = _nt_FWS
-
if r6
-
r5 = r6
-
else
-
r5 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s4 << r5
-
if r5
-
r7 = _nt_quoted_domain
-
s4 << r7
-
end
-
if s4.last
-
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
r4.extend(DomainText0)
-
else
-
@index = i4
-
r4 = nil
-
end
-
if r4
-
s3 << r4
-
else
-
break
-
end
-
end
-
if s3.empty?
-
@index = i3
-
r3 = nil
-
else
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
end
-
s1 << r3
-
if r3
-
r9 = _nt_FWS
-
if r9
-
r8 = r9
-
else
-
r8 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s1 << r8
-
if r8
-
r10 = _nt_DQUOTE
-
s1 << r10
-
end
-
end
-
end
-
20
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(DomainText1)
-
else
-
20
@index = i1
-
20
r1 = nil
-
end
-
20
if r1
-
r0 = r1
-
else
-
20
s11, i11 = [], index
-
20
loop do
-
68
r12 = _nt_atext
-
68
if r12
-
48
s11 << r12
-
else
-
20
break
-
end
-
end
-
20
if s11.empty?
-
8
@index = i11
-
8
r11 = nil
-
else
-
12
r11 = instantiate_node(SyntaxNode,input, i11...index, s11)
-
end
-
20
if r11
-
12
r0 = r11
-
else
-
8
@index = i0
-
8
r0 = nil
-
end
-
end
-
-
20
node_cache[:domain_text][start_index] = r0
-
-
20
r0
-
end
-
-
1
module QuotedDomain0
-
1
def text
-
elements[1]
-
end
-
end
-
-
1
def _nt_quoted_domain
-
start_index = index
-
if node_cache[:quoted_domain].has_key?(index)
-
cached = node_cache[:quoted_domain][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_qdcontent
-
if r1
-
r0 = r1
-
else
-
i2, s2 = index, []
-
if has_terminal?("\\", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("\\")
-
r3 = nil
-
end
-
s2 << r3
-
if r3
-
r4 = _nt_text
-
s2 << r4
-
end
-
if s2.last
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
r2.extend(QuotedDomain0)
-
else
-
@index = i2
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:quoted_domain][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_qdcontent
-
start_index = index
-
if node_cache[:qdcontent].has_key?(index)
-
cached = node_cache[:qdcontent][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_NO_WS_CTL
-
if r1
-
r0 = r1
-
else
-
if has_terminal?('\G[\\x21]', true, index)
-
r2 = true
-
@index += 1
-
else
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?('\G[\\x23-\\x45]', true, index)
-
r3 = true
-
@index += 1
-
else
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
if has_terminal?('\G[\\x47-\\x5b]', true, index)
-
r4 = true
-
@index += 1
-
else
-
r4 = nil
-
end
-
if r4
-
r0 = r4
-
else
-
if has_terminal?('\G[\\x5d-\\x7e]', true, index)
-
r5 = true
-
@index += 1
-
else
-
r5 = nil
-
end
-
if r5
-
r0 = r5
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
-
node_cache[:qdcontent][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_phrase
-
4
start_index = index
-
4
if node_cache[:phrase].has_key?(index)
-
cached = node_cache[:phrase][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
r1 = _nt_obs_phrase
-
4
if r1
-
4
r0 = r1
-
else
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_word
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
if s2.empty?
-
@index = i2
-
r2 = nil
-
else
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
end
-
if r2
-
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
4
node_cache[:phrase][start_index] = r0
-
-
4
r0
-
end
-
-
1
def _nt_word
-
16
start_index = index
-
16
if node_cache[:word].has_key?(index)
-
cached = node_cache[:word][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
16
i0 = index
-
16
r1 = _nt_atom
-
16
if r1
-
8
r0 = r1
-
else
-
8
r2 = _nt_quoted_string
-
8
if r2
-
r0 = r2
-
else
-
8
@index = i0
-
8
r0 = nil
-
end
-
end
-
-
16
node_cache[:word][start_index] = r0
-
-
16
r0
-
end
-
-
1
module PhraseList0
-
1
def phrase_value
-
elements[2]
-
end
-
end
-
-
1
module PhraseList1
-
1
def first_phrase
-
elements[0]
-
end
-
-
1
def other_phrases
-
elements[1]
-
end
-
end
-
-
1
def _nt_phrase_list
-
start_index = index
-
if node_cache[:phrase_list].has_key?(index)
-
cached = node_cache[:phrase_list][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_phrase
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
i3, s3 = index, []
-
if has_terminal?(",", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r4 = nil
-
end
-
s3 << r4
-
if r4
-
s5, i5 = [], index
-
loop do
-
r6 = _nt_FWS
-
if r6
-
s5 << r6
-
else
-
break
-
end
-
end
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
s3 << r5
-
if r5
-
r7 = _nt_phrase
-
s3 << r7
-
end
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(PhraseList0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(PhraseList1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:phrase_list][start_index] = r0
-
-
r0
-
end
-
-
1
module DomainLiteral0
-
1
def dcontent
-
elements[1]
-
end
-
end
-
-
1
module DomainLiteral1
-
end
-
-
1
def _nt_domain_literal
-
start_index = index
-
if node_cache[:domain_literal].has_key?(index)
-
cached = node_cache[:domain_literal][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
if has_terminal?("[", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("[")
-
r3 = nil
-
end
-
s0 << r3
-
if r3
-
s4, i4 = [], index
-
loop do
-
i5, s5 = index, []
-
r7 = _nt_FWS
-
if r7
-
r6 = r7
-
else
-
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s5 << r6
-
if r6
-
r8 = _nt_dcontent
-
s5 << r8
-
end
-
if s5.last
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
r5.extend(DomainLiteral0)
-
else
-
@index = i5
-
r5 = nil
-
end
-
if r5
-
s4 << r5
-
else
-
break
-
end
-
end
-
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
s0 << r4
-
if r4
-
r10 = _nt_FWS
-
if r10
-
r9 = r10
-
else
-
r9 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r9
-
if r9
-
if has_terminal?("]", false, index)
-
r11 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("]")
-
r11 = nil
-
end
-
s0 << r11
-
if r11
-
r13 = _nt_CFWS
-
if r13
-
r12 = r13
-
else
-
r12 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r12
-
end
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(DomainLiteral1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:domain_literal][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_dcontent
-
start_index = index
-
if node_cache[:dcontent].has_key?(index)
-
cached = node_cache[:dcontent][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_dtext
-
if r1
-
r0 = r1
-
else
-
r2 = _nt_quoted_pair
-
if r2
-
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:dcontent][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_dtext
-
start_index = index
-
if node_cache[:dtext].has_key?(index)
-
cached = node_cache[:dtext][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_NO_WS_CTL
-
if r1
-
r0 = r1
-
else
-
if has_terminal?('\G[\\x21-\\x5a]', true, index)
-
r2 = true
-
@index += 1
-
else
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?('\G[\\x5e-\\x7e]', true, index)
-
r3 = true
-
@index += 1
-
else
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
-
node_cache[:dtext][start_index] = r0
-
-
r0
-
end
-
-
1
module AngleAddr0
-
1
def addr_spec
-
50
elements[2]
-
end
-
-
end
-
-
1
def _nt_angle_addr
-
6
start_index = index
-
6
if node_cache[:angle_addr].has_key?(index)
-
cached = node_cache[:angle_addr][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
6
i0 = index
-
6
i1, s1 = index, []
-
6
r3 = _nt_CFWS
-
6
if r3
-
6
r2 = r3
-
else
-
r2 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
6
s1 << r2
-
6
if r2
-
6
if has_terminal?("<", false, index)
-
2
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
2
@index += 1
-
else
-
4
terminal_parse_failure("<")
-
4
r4 = nil
-
end
-
6
s1 << r4
-
6
if r4
-
2
r5 = _nt_addr_spec
-
2
s1 << r5
-
2
if r5
-
2
if has_terminal?(">", false, index)
-
2
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
2
@index += 1
-
else
-
terminal_parse_failure(">")
-
r6 = nil
-
end
-
2
s1 << r6
-
2
if r6
-
2
r8 = _nt_CFWS
-
2
if r8
-
2
r7 = r8
-
else
-
r7 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
2
s1 << r7
-
end
-
end
-
end
-
end
-
6
if s1.last
-
2
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
2
r1.extend(AngleAddr0)
-
else
-
4
@index = i1
-
4
r1 = nil
-
end
-
6
if r1
-
2
r0 = r1
-
else
-
4
r9 = _nt_obs_angle_addr
-
4
if r9
-
r0 = r9
-
else
-
4
@index = i0
-
4
r0 = nil
-
end
-
end
-
-
6
node_cache[:angle_addr][start_index] = r0
-
-
6
r0
-
end
-
-
1
module AddrSpec0
-
1
def local_part
-
24
elements[0]
-
end
-
-
1
def domain
-
4
elements[2]
-
end
-
end
-
-
1
def _nt_addr_spec
-
4
start_index = index
-
4
if node_cache[:addr_spec].has_key?(index)
-
cached = node_cache[:addr_spec][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
i1, s1 = index, []
-
4
r2 = _nt_local_part
-
4
s1 << r2
-
4
if r2
-
4
if has_terminal?("@", false, index)
-
4
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
4
@index += 1
-
else
-
terminal_parse_failure("@")
-
r3 = nil
-
end
-
4
s1 << r3
-
4
if r3
-
4
r4 = _nt_domain
-
4
s1 << r4
-
end
-
end
-
4
if s1.last
-
4
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
4
r1.extend(AddrSpec0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
4
if r1
-
4
r0 = r1
-
else
-
r5 = _nt_local_part
-
if r5
-
r0 = r5
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
4
node_cache[:addr_spec][start_index] = r0
-
-
4
r0
-
end
-
-
1
def _nt_local_part
-
4
start_index = index
-
4
if node_cache[:local_part].has_key?(index)
-
cached = node_cache[:local_part][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
r1 = _nt_local_dot_atom
-
4
if r1
-
4
r0 = r1
-
else
-
r2 = _nt_quoted_string
-
if r2
-
r0 = r2
-
else
-
r3 = _nt_obs_local_part
-
if r3
-
r0 = r3
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
-
4
node_cache[:local_part][start_index] = r0
-
-
4
r0
-
end
-
-
1
def _nt_domain
-
4
start_index = index
-
4
if node_cache[:domain].has_key?(index)
-
cached = node_cache[:domain][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
r1 = _nt_dot_atom
-
4
if r1
-
4
r0 = r1
-
else
-
r2 = _nt_domain_literal
-
if r2
-
r0 = r2
-
else
-
r3 = _nt_obs_domain
-
if r3
-
r0 = r3
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
-
4
node_cache[:domain][start_index] = r0
-
-
4
r0
-
end
-
-
1
module Group0
-
1
def group_name
-
elements[0]
-
end
-
-
1
def group_list
-
elements[2]
-
end
-
-
end
-
-
1
def _nt_group
-
4
start_index = index
-
4
if node_cache[:group].has_key?(index)
-
cached = node_cache[:group][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0, s0 = index, []
-
4
r1 = _nt_display_name
-
4
s0 << r1
-
4
if r1
-
4
if has_terminal?(":", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
4
terminal_parse_failure(":")
-
4
r2 = nil
-
end
-
4
s0 << r2
-
4
if r2
-
i4 = index
-
r5 = _nt_mailbox_list_group
-
if r5
-
r4 = r5
-
else
-
r6 = _nt_CFWS
-
if r6
-
r4 = r6
-
else
-
@index = i4
-
r4 = nil
-
end
-
end
-
if r4
-
r3 = r4
-
else
-
r3 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r3
-
if r3
-
if has_terminal?(";", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(";")
-
r7 = nil
-
end
-
s0 << r7
-
if r7
-
r9 = _nt_CFWS
-
if r9
-
r8 = r9
-
else
-
r8 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r8
-
end
-
end
-
end
-
end
-
4
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Group0)
-
else
-
4
@index = i0
-
4
r0 = nil
-
end
-
-
4
node_cache[:group][start_index] = r0
-
-
4
r0
-
end
-
-
1
module MailboxListGroup0
-
1
def addresses
-
[first_addr] + other_addr.elements.map { |o| o.addr_value }
-
end
-
end
-
-
1
def _nt_mailbox_list_group
-
start_index = index
-
if node_cache[:mailbox_list_group].has_key?(index)
-
cached = node_cache[:mailbox_list_group][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
r0 = _nt_mailbox_list
-
r0.extend(MailboxListGroup0)
-
-
node_cache[:mailbox_list_group][start_index] = r0
-
-
r0
-
end
-
-
1
module QuotedString0
-
1
def qcontent
-
elements[1]
-
end
-
end
-
-
1
module QuotedString1
-
1
def DQUOTE1
-
elements[1]
-
end
-
-
1
def quoted_content
-
1
elements[2]
-
end
-
-
1
def DQUOTE2
-
elements[4]
-
end
-
-
end
-
-
1
def _nt_quoted_string
-
10
start_index = index
-
10
if node_cache[:quoted_string].has_key?(index)
-
cached = node_cache[:quoted_string][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
10
i0, s0 = index, []
-
10
r2 = _nt_CFWS
-
10
if r2
-
10
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
10
s0 << r1
-
10
if r1
-
10
r3 = _nt_DQUOTE
-
10
s0 << r3
-
10
if r3
-
1
s4, i4 = [], index
-
1
loop do
-
44
i5, s5 = index, []
-
44
r7 = _nt_FWS
-
44
if r7
-
r6 = r7
-
else
-
44
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
44
s5 << r6
-
44
if r6
-
44
r8 = _nt_qcontent
-
44
s5 << r8
-
end
-
44
if s5.last
-
43
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
43
r5.extend(QuotedString0)
-
else
-
1
@index = i5
-
1
r5 = nil
-
end
-
44
if r5
-
43
s4 << r5
-
else
-
1
break
-
end
-
end
-
1
if s4.empty?
-
@index = i4
-
r4 = nil
-
else
-
1
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
end
-
1
s0 << r4
-
1
if r4
-
1
r10 = _nt_FWS
-
1
if r10
-
r9 = r10
-
else
-
1
r9 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
1
s0 << r9
-
1
if r9
-
1
r11 = _nt_DQUOTE
-
1
s0 << r11
-
1
if r11
-
1
r13 = _nt_CFWS
-
1
if r13
-
1
r12 = r13
-
else
-
r12 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
1
s0 << r12
-
end
-
end
-
end
-
end
-
end
-
10
if s0.last
-
1
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
1
r0.extend(QuotedString1)
-
else
-
9
@index = i0
-
9
r0 = nil
-
end
-
-
10
node_cache[:quoted_string][start_index] = r0
-
-
10
r0
-
end
-
-
1
def _nt_qcontent
-
44
start_index = index
-
44
if node_cache[:qcontent].has_key?(index)
-
cached = node_cache[:qcontent][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
44
i0 = index
-
44
r1 = _nt_qtext
-
44
if r1
-
43
r0 = r1
-
else
-
1
r2 = _nt_quoted_pair
-
1
if r2
-
r0 = r2
-
else
-
1
@index = i0
-
1
r0 = nil
-
end
-
end
-
-
44
node_cache[:qcontent][start_index] = r0
-
-
44
r0
-
end
-
-
1
module QuotedPair0
-
1
def text
-
elements[1]
-
end
-
end
-
-
1
def _nt_quoted_pair
-
1
start_index = index
-
1
if node_cache[:quoted_pair].has_key?(index)
-
cached = node_cache[:quoted_pair][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
1
i0 = index
-
1
i1, s1 = index, []
-
1
if has_terminal?("\\", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
1
terminal_parse_failure("\\")
-
1
r2 = nil
-
end
-
1
s1 << r2
-
1
if r2
-
r3 = _nt_text
-
s1 << r3
-
end
-
1
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(QuotedPair0)
-
else
-
1
@index = i1
-
1
r1 = nil
-
end
-
1
if r1
-
r0 = r1
-
else
-
1
r4 = _nt_obs_qp
-
1
if r4
-
r0 = r4
-
else
-
1
@index = i0
-
1
r0 = nil
-
end
-
end
-
-
1
node_cache[:quoted_pair][start_index] = r0
-
-
1
r0
-
end
-
-
1
def _nt_qtext
-
44
start_index = index
-
44
if node_cache[:qtext].has_key?(index)
-
cached = node_cache[:qtext][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
44
i0 = index
-
44
r1 = _nt_NO_WS_CTL
-
44
if r1
-
r0 = r1
-
else
-
44
if has_terminal?('\G[\\x21]', true, index)
-
r2 = true
-
@index += 1
-
else
-
44
r2 = nil
-
end
-
44
if r2
-
r0 = r2
-
else
-
44
if has_terminal?('\G[\\x23-\\x5b]', true, index)
-
26
r3 = true
-
26
@index += 1
-
else
-
18
r3 = nil
-
end
-
44
if r3
-
26
r0 = r3
-
else
-
18
if has_terminal?('\G[\\x5d-\\x7e]', true, index)
-
17
r4 = true
-
17
@index += 1
-
else
-
1
r4 = nil
-
end
-
18
if r4
-
17
r0 = r4
-
else
-
1
@index = i0
-
1
r0 = nil
-
end
-
end
-
end
-
end
-
-
44
node_cache[:qtext][start_index] = r0
-
-
44
r0
-
end
-
-
1
def _nt_text
-
start_index = index
-
if node_cache[:text].has_key?(index)
-
cached = node_cache[:text][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
if has_terminal?('\G[\\x01-\\x09]', true, index)
-
r1 = true
-
@index += 1
-
else
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
if has_terminal?('\G[\\x0b-\\x0c]', true, index)
-
r2 = true
-
@index += 1
-
else
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?('\G[\\x0e-\\x7e]', true, index)
-
r3 = true
-
@index += 1
-
else
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
r4 = _nt_obs_text
-
if r4
-
r0 = r4
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
-
node_cache[:text][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_display_name
-
8
start_index = index
-
8
if node_cache[:display_name].has_key?(index)
-
4
cached = node_cache[:display_name][index]
-
4
if cached
-
4
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
4
@index = cached.interval.end
-
end
-
4
return cached
-
end
-
-
4
r0 = _nt_phrase
-
-
4
node_cache[:display_name][start_index] = r0
-
-
4
r0
-
end
-
-
1
module NameAddr0
-
1
def display_name
-
2
elements[0]
-
end
-
-
1
def angle_addr
-
92
elements[1]
-
end
-
end
-
-
1
def _nt_name_addr
-
4
start_index = index
-
4
if node_cache[:name_addr].has_key?(index)
-
cached = node_cache[:name_addr][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
i1, s1 = index, []
-
4
r2 = _nt_display_name
-
4
s1 << r2
-
4
if r2
-
4
r3 = _nt_angle_addr
-
4
s1 << r3
-
end
-
4
if s1.last
-
2
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
2
r1.extend(NameAddr0)
-
else
-
2
@index = i1
-
2
r1 = nil
-
end
-
4
if r1
-
2
r0 = r1
-
else
-
2
r4 = _nt_angle_addr
-
2
if r4
-
r0 = r4
-
else
-
2
@index = i0
-
2
r0 = nil
-
end
-
end
-
-
4
node_cache[:name_addr][start_index] = r0
-
-
4
r0
-
end
-
-
1
module MailboxList0
-
1
def addr_value
-
elements[1]
-
end
-
end
-
-
1
module MailboxList1
-
1
def first_addr
-
elements[0]
-
end
-
-
1
def other_addr
-
elements[1]
-
end
-
end
-
-
1
def _nt_mailbox_list
-
start_index = index
-
if node_cache[:mailbox_list].has_key?(index)
-
cached = node_cache[:mailbox_list][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
r2 = _nt_mailbox
-
s1 << r2
-
if r2
-
s3, i3 = [], index
-
loop do
-
i4, s4 = index, []
-
if has_terminal?(",", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r5 = nil
-
end
-
s4 << r5
-
if r5
-
r6 = _nt_mailbox
-
s4 << r6
-
end
-
if s4.last
-
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
r4.extend(MailboxList0)
-
else
-
@index = i4
-
r4 = nil
-
end
-
if r4
-
s3 << r4
-
else
-
break
-
end
-
end
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
s1 << r3
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(MailboxList1)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r7 = _nt_obs_mbox_list
-
if r7
-
r0 = r7
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:mailbox_list][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_mailbox
-
4
start_index = index
-
4
if node_cache[:mailbox].has_key?(index)
-
cached = node_cache[:mailbox][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
r1 = _nt_name_addr
-
4
if r1
-
2
r0 = r1
-
else
-
2
r2 = _nt_addr_spec
-
2
if r2
-
2
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
4
node_cache[:mailbox][start_index] = r0
-
-
4
r0
-
end
-
-
1
module Address0
-
-
1
def dig_comments(comments, elements)
-
elements.each { |elem|
-
if elem.respond_to?(:comment)
-
comments << elem.comment
-
end
-
dig_comments(comments, elem.elements) if elem.elements
-
}
-
end
-
-
1
def comments
-
comments = []
-
dig_comments(comments, elements)
-
comments
-
end
-
end
-
-
1
module Address1
-
-
1
def dig_comments(comments, elements)
-
1048
elements.each { |elem|
-
1912
if elem.respond_to?(:comment)
-
comments << elem.comment
-
end
-
1912
dig_comments(comments, elem.elements) if elem.elements
-
}
-
end
-
-
1
def comments
-
38
comments = []
-
38
dig_comments(comments, elements)
-
38
comments
-
end
-
end
-
-
1
def _nt_address
-
4
start_index = index
-
4
if node_cache[:address].has_key?(index)
-
cached = node_cache[:address][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
r1 = _nt_group
-
4
r1.extend(Address0)
-
4
if r1
-
r0 = r1
-
else
-
4
r2 = _nt_mailbox
-
4
r2.extend(Address1)
-
4
if r2
-
4
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
4
node_cache[:address][start_index] = r0
-
-
4
r0
-
end
-
-
1
module AddressList0
-
1
def addr_value
-
elements[3]
-
end
-
end
-
-
1
module AddressList1
-
1
def first_addr
-
4
elements[0]
-
end
-
-
1
def other_addr
-
4
elements[1]
-
end
-
end
-
-
1
def _nt_address_list
-
4
start_index = index
-
4
if node_cache[:address_list].has_key?(index)
-
cached = node_cache[:address_list][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0, s0 = index, []
-
4
r2 = _nt_address
-
4
if r2
-
4
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
4
s0 << r1
-
4
if r1
-
4
s3, i3 = [], index
-
4
loop do
-
4
i4, s4 = index, []
-
4
s5, i5 = [], index
-
4
loop do
-
4
r6 = _nt_FWS
-
4
if r6
-
s5 << r6
-
else
-
4
break
-
end
-
end
-
4
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
4
s4 << r5
-
4
if r5
-
4
if has_terminal?(",", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
4
terminal_parse_failure(",")
-
4
r7 = nil
-
end
-
4
s4 << r7
-
4
if r7
-
s8, i8 = [], index
-
loop do
-
r9 = _nt_FWS
-
if r9
-
s8 << r9
-
else
-
break
-
end
-
end
-
r8 = instantiate_node(SyntaxNode,input, i8...index, s8)
-
s4 << r8
-
if r8
-
r11 = _nt_address
-
if r11
-
r10 = r11
-
else
-
r10 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s4 << r10
-
end
-
end
-
end
-
4
if s4.last
-
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
r4.extend(AddressList0)
-
else
-
4
@index = i4
-
4
r4 = nil
-
end
-
4
if r4
-
s3 << r4
-
else
-
4
break
-
end
-
end
-
4
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
4
s0 << r3
-
end
-
4
if s0.last
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
4
r0.extend(AddressList1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
4
node_cache[:address_list][start_index] = r0
-
-
4
r0
-
end
-
-
1
module DateTime0
-
1
def day_of_week
-
elements[0]
-
end
-
-
end
-
-
1
module DateTime1
-
1
def date
-
elements[1]
-
end
-
-
1
def FWS
-
elements[2]
-
end
-
-
1
def time
-
elements[3]
-
end
-
-
end
-
-
1
def _nt_date_time
-
start_index = index
-
if node_cache[:date_time].has_key?(index)
-
cached = node_cache[:date_time][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
i2, s2 = index, []
-
r3 = _nt_day_of_week
-
s2 << r3
-
if r3
-
if has_terminal?(",", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r4 = nil
-
end
-
s2 << r4
-
end
-
if s2.last
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
r2.extend(DateTime0)
-
else
-
@index = i2
-
r2 = nil
-
end
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
r5 = _nt_date
-
s0 << r5
-
if r5
-
r6 = _nt_FWS
-
s0 << r6
-
if r6
-
r7 = _nt_time
-
s0 << r7
-
if r7
-
r9 = _nt_CFWS
-
if r9
-
r8 = r9
-
else
-
r8 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r8
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(DateTime1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:date_time][start_index] = r0
-
-
r0
-
end
-
-
1
module DayOfWeek0
-
1
def day_name
-
elements[1]
-
end
-
end
-
-
1
def _nt_day_of_week
-
start_index = index
-
if node_cache[:day_of_week].has_key?(index)
-
cached = node_cache[:day_of_week][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
r3 = _nt_FWS
-
if r3
-
r2 = r3
-
else
-
r2 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s1 << r2
-
if r2
-
r4 = _nt_day_name
-
s1 << r4
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(DayOfWeek0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r5 = _nt_obs_day_of_week
-
if r5
-
r0 = r5
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:day_of_week][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_day_name
-
start_index = index
-
if node_cache[:day_name].has_key?(index)
-
cached = node_cache[:day_name][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
if has_terminal?("Mon", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Mon")
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
if has_terminal?("Tue", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Tue")
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?("Wed", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Wed")
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
if has_terminal?("Thu", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Thu")
-
r4 = nil
-
end
-
if r4
-
r0 = r4
-
else
-
if has_terminal?("Fri", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Fri")
-
r5 = nil
-
end
-
if r5
-
r0 = r5
-
else
-
if has_terminal?("Sat", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Sat")
-
r6 = nil
-
end
-
if r6
-
r0 = r6
-
else
-
if has_terminal?("Sun", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Sun")
-
r7 = nil
-
end
-
if r7
-
r0 = r7
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
-
node_cache[:day_name][start_index] = r0
-
-
r0
-
end
-
-
1
module Date0
-
1
def day
-
elements[0]
-
end
-
-
1
def month
-
elements[1]
-
end
-
-
1
def year
-
elements[2]
-
end
-
end
-
-
1
def _nt_date
-
start_index = index
-
if node_cache[:date].has_key?(index)
-
cached = node_cache[:date][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_day
-
s0 << r1
-
if r1
-
r2 = _nt_month
-
s0 << r2
-
if r2
-
r3 = _nt_year
-
s0 << r3
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Date0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:date][start_index] = r0
-
-
r0
-
end
-
-
1
module Year0
-
1
def DIGIT1
-
elements[0]
-
end
-
-
1
def DIGIT2
-
elements[1]
-
end
-
-
1
def DIGIT3
-
elements[2]
-
end
-
-
1
def DIGIT4
-
elements[3]
-
end
-
end
-
-
1
def _nt_year
-
start_index = index
-
if node_cache[:year].has_key?(index)
-
cached = node_cache[:year][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
r2 = _nt_DIGIT
-
s1 << r2
-
if r2
-
r3 = _nt_DIGIT
-
s1 << r3
-
if r3
-
r4 = _nt_DIGIT
-
s1 << r4
-
if r4
-
r5 = _nt_DIGIT
-
s1 << r5
-
end
-
end
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(Year0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r6 = _nt_obs_year
-
if r6
-
r0 = r6
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:year][start_index] = r0
-
-
r0
-
end
-
-
1
module Month0
-
1
def FWS1
-
elements[0]
-
end
-
-
1
def month_name
-
elements[1]
-
end
-
-
1
def FWS2
-
elements[2]
-
end
-
end
-
-
1
def _nt_month
-
start_index = index
-
if node_cache[:month].has_key?(index)
-
cached = node_cache[:month][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
r2 = _nt_FWS
-
s1 << r2
-
if r2
-
r3 = _nt_month_name
-
s1 << r3
-
if r3
-
r4 = _nt_FWS
-
s1 << r4
-
end
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(Month0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r5 = _nt_obs_month
-
if r5
-
r0 = r5
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:month][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_month_name
-
start_index = index
-
if node_cache[:month_name].has_key?(index)
-
cached = node_cache[:month_name][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
if has_terminal?("Jan", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Jan")
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
if has_terminal?("Feb", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Feb")
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?("Mar", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Mar")
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
if has_terminal?("Apr", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Apr")
-
r4 = nil
-
end
-
if r4
-
r0 = r4
-
else
-
if has_terminal?("May", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("May")
-
r5 = nil
-
end
-
if r5
-
r0 = r5
-
else
-
if has_terminal?("Jun", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Jun")
-
r6 = nil
-
end
-
if r6
-
r0 = r6
-
else
-
if has_terminal?("Jul", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Jul")
-
r7 = nil
-
end
-
if r7
-
r0 = r7
-
else
-
if has_terminal?("Aug", false, index)
-
r8 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Aug")
-
r8 = nil
-
end
-
if r8
-
r0 = r8
-
else
-
if has_terminal?("Sep", false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Sep")
-
r9 = nil
-
end
-
if r9
-
r0 = r9
-
else
-
if has_terminal?("Oct", false, index)
-
r10 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Oct")
-
r10 = nil
-
end
-
if r10
-
r0 = r10
-
else
-
if has_terminal?("Nov", false, index)
-
r11 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Nov")
-
r11 = nil
-
end
-
if r11
-
r0 = r11
-
else
-
if has_terminal?("Dec", false, index)
-
r12 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Dec")
-
r12 = nil
-
end
-
if r12
-
r0 = r12
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
-
node_cache[:month_name][start_index] = r0
-
-
r0
-
end
-
-
1
module Day0
-
1
def DIGIT
-
elements[1]
-
end
-
-
end
-
-
1
def _nt_day
-
start_index = index
-
if node_cache[:day].has_key?(index)
-
cached = node_cache[:day][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
r3 = _nt_FWS
-
if r3
-
r2 = r3
-
else
-
r2 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s1 << r2
-
if r2
-
r4 = _nt_DIGIT
-
s1 << r4
-
if r4
-
r6 = _nt_DIGIT
-
if r6
-
r5 = r6
-
else
-
r5 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s1 << r5
-
end
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(Day0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r7 = _nt_obs_day
-
if r7
-
r0 = r7
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:day][start_index] = r0
-
-
r0
-
end
-
-
1
module Time0
-
1
def time_of_day
-
elements[0]
-
end
-
-
1
def FWS
-
elements[1]
-
end
-
-
1
def zone
-
elements[2]
-
end
-
end
-
-
1
def _nt_time
-
start_index = index
-
if node_cache[:time].has_key?(index)
-
cached = node_cache[:time][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_time_of_day
-
s0 << r1
-
if r1
-
r2 = _nt_FWS
-
s0 << r2
-
if r2
-
r3 = _nt_zone
-
s0 << r3
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Time0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:time][start_index] = r0
-
-
r0
-
end
-
-
1
module TimeOfDay0
-
1
def second
-
elements[1]
-
end
-
end
-
-
1
module TimeOfDay1
-
1
def hour
-
elements[0]
-
end
-
-
1
def minute
-
elements[2]
-
end
-
-
end
-
-
1
def _nt_time_of_day
-
start_index = index
-
if node_cache[:time_of_day].has_key?(index)
-
cached = node_cache[:time_of_day][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_hour
-
s0 << r1
-
if r1
-
if has_terminal?(":", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r2 = nil
-
end
-
s0 << r2
-
if r2
-
r3 = _nt_minute
-
s0 << r3
-
if r3
-
i5, s5 = index, []
-
if has_terminal?(":", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r6 = nil
-
end
-
s5 << r6
-
if r6
-
r7 = _nt_second
-
s5 << r7
-
end
-
if s5.last
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
r5.extend(TimeOfDay0)
-
else
-
@index = i5
-
r5 = nil
-
end
-
if r5
-
r4 = r5
-
else
-
r4 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r4
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(TimeOfDay1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:time_of_day][start_index] = r0
-
-
r0
-
end
-
-
1
module Hour0
-
1
def DIGIT1
-
elements[0]
-
end
-
-
1
def DIGIT2
-
elements[1]
-
end
-
end
-
-
1
def _nt_hour
-
start_index = index
-
if node_cache[:hour].has_key?(index)
-
cached = node_cache[:hour][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
r2 = _nt_DIGIT
-
s1 << r2
-
if r2
-
r3 = _nt_DIGIT
-
s1 << r3
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(Hour0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r4 = _nt_obs_hour
-
if r4
-
r0 = r4
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:hour][start_index] = r0
-
-
r0
-
end
-
-
1
module Minute0
-
1
def DIGIT1
-
elements[0]
-
end
-
-
1
def DIGIT2
-
elements[1]
-
end
-
end
-
-
1
def _nt_minute
-
start_index = index
-
if node_cache[:minute].has_key?(index)
-
cached = node_cache[:minute][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
r2 = _nt_DIGIT
-
s1 << r2
-
if r2
-
r3 = _nt_DIGIT
-
s1 << r3
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(Minute0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r4 = _nt_obs_minute
-
if r4
-
r0 = r4
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:minute][start_index] = r0
-
-
r0
-
end
-
-
1
module Second0
-
1
def DIGIT1
-
elements[0]
-
end
-
-
1
def DIGIT2
-
elements[1]
-
end
-
end
-
-
1
def _nt_second
-
start_index = index
-
if node_cache[:second].has_key?(index)
-
cached = node_cache[:second][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
r2 = _nt_DIGIT
-
s1 << r2
-
if r2
-
r3 = _nt_DIGIT
-
s1 << r3
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(Second0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r4 = _nt_obs_second
-
if r4
-
r0 = r4
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:second][start_index] = r0
-
-
r0
-
end
-
-
1
module Zone0
-
1
def DIGIT1
-
elements[1]
-
end
-
-
1
def DIGIT2
-
elements[2]
-
end
-
-
1
def DIGIT3
-
elements[3]
-
end
-
-
1
def DIGIT4
-
elements[4]
-
end
-
end
-
-
1
def _nt_zone
-
start_index = index
-
if node_cache[:zone].has_key?(index)
-
cached = node_cache[:zone][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
i2 = index
-
if has_terminal?("+", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("+")
-
r3 = nil
-
end
-
if r3
-
r2 = r3
-
else
-
if has_terminal?("-", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("-")
-
r4 = nil
-
end
-
if r4
-
r2 = r4
-
else
-
@index = i2
-
r2 = nil
-
end
-
end
-
s1 << r2
-
if r2
-
r5 = _nt_DIGIT
-
s1 << r5
-
if r5
-
r6 = _nt_DIGIT
-
s1 << r6
-
if r6
-
r7 = _nt_DIGIT
-
s1 << r7
-
if r7
-
r8 = _nt_DIGIT
-
s1 << r8
-
end
-
end
-
end
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(Zone0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r9 = _nt_obs_zone
-
if r9
-
r0 = r9
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:zone][start_index] = r0
-
-
r0
-
end
-
-
1
module Return0
-
1
def path
-
elements[0]
-
end
-
-
1
def CRLF
-
elements[1]
-
end
-
end
-
-
1
def _nt_return
-
start_index = index
-
if node_cache[:return].has_key?(index)
-
cached = node_cache[:return][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_path
-
s0 << r1
-
if r1
-
r2 = _nt_CRLF
-
s0 << r2
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Return0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:return][start_index] = r0
-
-
r0
-
end
-
-
1
module Path0
-
end
-
-
1
def _nt_path
-
start_index = index
-
if node_cache[:path].has_key?(index)
-
cached = node_cache[:path][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
i1, s1 = index, []
-
r3 = _nt_CFWS
-
if r3
-
r2 = r3
-
else
-
r2 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s1 << r2
-
if r2
-
if has_terminal?("<", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("<")
-
r4 = nil
-
end
-
s1 << r4
-
if r4
-
i5 = index
-
r7 = _nt_CFWS
-
if r7
-
r6 = r7
-
else
-
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
if r6
-
r5 = r6
-
else
-
r8 = _nt_addr_spec
-
if r8
-
r5 = r8
-
else
-
@index = i5
-
r5 = nil
-
end
-
end
-
s1 << r5
-
if r5
-
if has_terminal?(">", false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(">")
-
r9 = nil
-
end
-
s1 << r9
-
if r9
-
r11 = _nt_CFWS
-
if r11
-
r10 = r11
-
else
-
r10 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s1 << r10
-
end
-
end
-
end
-
end
-
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(Path0)
-
else
-
@index = i1
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
r12 = _nt_obs_path
-
if r12
-
r0 = r12
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:path][start_index] = r0
-
-
r0
-
end
-
-
1
module Received0
-
1
def name_val_list
-
elements[0]
-
end
-
-
1
def date_time
-
elements[2]
-
end
-
-
1
def CRLF
-
elements[3]
-
end
-
end
-
-
1
def _nt_received
-
start_index = index
-
if node_cache[:received].has_key?(index)
-
cached = node_cache[:received][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_name_val_list
-
s0 << r1
-
if r1
-
if has_terminal?(";", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(";")
-
r2 = nil
-
end
-
s0 << r2
-
if r2
-
r3 = _nt_date_time
-
s0 << r3
-
if r3
-
r4 = _nt_CRLF
-
s0 << r4
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(Received0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:received][start_index] = r0
-
-
r0
-
end
-
-
1
module NameValList0
-
1
def CFWS
-
elements[0]
-
end
-
-
1
def name_val_pair
-
elements[1]
-
end
-
end
-
-
1
module NameValList1
-
1
def name_val_pair
-
elements[0]
-
end
-
-
end
-
-
1
module NameValList2
-
end
-
-
1
def _nt_name_val_list
-
start_index = index
-
if node_cache[:name_val_list].has_key?(index)
-
cached = node_cache[:name_val_list][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
i3, s3 = index, []
-
r4 = _nt_name_val_pair
-
s3 << r4
-
if r4
-
s5, i5 = [], index
-
loop do
-
i6, s6 = index, []
-
r7 = _nt_CFWS
-
s6 << r7
-
if r7
-
r8 = _nt_name_val_pair
-
s6 << r8
-
end
-
if s6.last
-
r6 = instantiate_node(SyntaxNode,input, i6...index, s6)
-
r6.extend(NameValList0)
-
else
-
@index = i6
-
r6 = nil
-
end
-
if r6
-
s5 << r6
-
else
-
break
-
end
-
end
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
s3 << r5
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(NameValList1)
-
else
-
@index = i3
-
r3 = nil
-
end
-
s0 << r3
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(NameValList2)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:name_val_list][start_index] = r0
-
-
r0
-
end
-
-
1
module NameValPair0
-
1
def item_name
-
elements[0]
-
end
-
-
1
def CFWS
-
elements[1]
-
end
-
-
1
def item_value
-
elements[2]
-
end
-
end
-
-
1
def _nt_name_val_pair
-
start_index = index
-
if node_cache[:name_val_pair].has_key?(index)
-
cached = node_cache[:name_val_pair][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_item_name
-
s0 << r1
-
if r1
-
r2 = _nt_CFWS
-
s0 << r2
-
if r2
-
r3 = _nt_item_value
-
s0 << r3
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(NameValPair0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:name_val_pair][start_index] = r0
-
-
r0
-
end
-
-
1
module ItemName0
-
end
-
-
1
module ItemName1
-
1
def ALPHA
-
elements[0]
-
end
-
-
end
-
-
1
def _nt_item_name
-
start_index = index
-
if node_cache[:item_name].has_key?(index)
-
cached = node_cache[:item_name][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_ALPHA
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
i3, s3 = index, []
-
if has_terminal?("-", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("-")
-
r5 = nil
-
end
-
if r5
-
r4 = r5
-
else
-
r4 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s3 << r4
-
if r4
-
i6 = index
-
r7 = _nt_ALPHA
-
if r7
-
r6 = r7
-
else
-
r8 = _nt_DIGIT
-
if r8
-
r6 = r8
-
else
-
@index = i6
-
r6 = nil
-
end
-
end
-
s3 << r6
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(ItemName0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ItemName1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:item_name][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_item_value
-
start_index = index
-
if node_cache[:item_value].has_key?(index)
-
cached = node_cache[:item_value][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
s1, i1 = [], index
-
loop do
-
r2 = _nt_angle_addr
-
if r2
-
s1 << r2
-
else
-
break
-
end
-
end
-
if s1.empty?
-
@index = i1
-
r1 = nil
-
else
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
end
-
if r1
-
r0 = r1
-
else
-
r3 = _nt_addr_spec
-
if r3
-
r0 = r3
-
else
-
r4 = _nt_atom
-
if r4
-
r0 = r4
-
else
-
r5 = _nt_domain
-
if r5
-
r0 = r5
-
else
-
r6 = _nt_msg_id
-
if r6
-
r0 = r6
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
-
node_cache[:item_value][start_index] = r0
-
-
r0
-
end
-
-
1
module MessageIds0
-
1
def CFWS
-
elements[0]
-
end
-
-
1
def msg_id_value
-
elements[1]
-
end
-
end
-
-
1
module MessageIds1
-
1
def first_msg_id
-
4
elements[0]
-
end
-
-
1
def other_msg_ids
-
4
elements[1]
-
end
-
end
-
-
1
def _nt_message_ids
-
4
start_index = index
-
4
if node_cache[:message_ids].has_key?(index)
-
cached = node_cache[:message_ids][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0, s0 = index, []
-
4
r1 = _nt_msg_id
-
4
s0 << r1
-
4
if r1
-
4
s2, i2 = [], index
-
4
loop do
-
4
i3, s3 = index, []
-
4
r4 = _nt_CFWS
-
4
s3 << r4
-
4
if r4
-
4
r5 = _nt_msg_id
-
4
s3 << r5
-
end
-
4
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(MessageIds0)
-
else
-
4
@index = i3
-
4
r3 = nil
-
end
-
4
if r3
-
s2 << r3
-
else
-
4
break
-
end
-
end
-
4
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
4
s0 << r2
-
end
-
4
if s0.last
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
4
r0.extend(MessageIds1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
4
node_cache[:message_ids][start_index] = r0
-
-
4
r0
-
end
-
-
1
module MsgId0
-
1
def msg_id_value
-
elements[2]
-
end
-
-
end
-
-
1
def _nt_msg_id
-
8
start_index = index
-
8
if node_cache[:msg_id].has_key?(index)
-
cached = node_cache[:msg_id][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
8
i0, s0 = index, []
-
8
r2 = _nt_CFWS
-
8
if r2
-
8
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
8
s0 << r1
-
8
if r1
-
8
if has_terminal?("<", false, index)
-
4
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
4
@index += 1
-
else
-
4
terminal_parse_failure("<")
-
4
r3 = nil
-
end
-
8
s0 << r3
-
8
if r3
-
4
r4 = _nt_msg_id_value
-
4
s0 << r4
-
4
if r4
-
4
if has_terminal?(">", false, index)
-
4
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
4
@index += 1
-
else
-
terminal_parse_failure(">")
-
r5 = nil
-
end
-
4
s0 << r5
-
4
if r5
-
4
r7 = _nt_CFWS
-
4
if r7
-
4
r6 = r7
-
else
-
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
4
s0 << r6
-
end
-
end
-
end
-
end
-
8
if s0.last
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
4
r0.extend(MsgId0)
-
else
-
4
@index = i0
-
4
r0 = nil
-
end
-
-
8
node_cache[:msg_id][start_index] = r0
-
-
8
r0
-
end
-
-
1
module MsgIdValue0
-
1
def id_left
-
elements[0]
-
end
-
-
1
def id_right
-
elements[2]
-
end
-
end
-
-
1
def _nt_msg_id_value
-
4
start_index = index
-
4
if node_cache[:msg_id_value].has_key?(index)
-
cached = node_cache[:msg_id_value][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0, s0 = index, []
-
4
r1 = _nt_id_left
-
4
s0 << r1
-
4
if r1
-
4
if has_terminal?("@", false, index)
-
4
r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
4
@index += 1
-
else
-
terminal_parse_failure("@")
-
r2 = nil
-
end
-
4
s0 << r2
-
4
if r2
-
4
r3 = _nt_id_right
-
4
s0 << r3
-
end
-
end
-
4
if s0.last
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
4
r0.extend(MsgIdValue0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
4
node_cache[:msg_id_value][start_index] = r0
-
-
4
r0
-
end
-
-
1
def _nt_id_left
-
4
start_index = index
-
4
if node_cache[:id_left].has_key?(index)
-
cached = node_cache[:id_left][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
r1 = _nt_message_id_text
-
4
if r1
-
4
r0 = r1
-
else
-
r2 = _nt_no_fold_quote
-
if r2
-
r0 = r2
-
else
-
r3 = _nt_obs_id_left
-
if r3
-
r0 = r3
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
-
4
node_cache[:id_left][start_index] = r0
-
-
4
r0
-
end
-
-
1
def _nt_id_right
-
4
start_index = index
-
4
if node_cache[:id_right].has_key?(index)
-
cached = node_cache[:id_right][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0 = index
-
4
r1 = _nt_msg_id_dot_atom_text
-
4
if r1
-
4
r0 = r1
-
else
-
r2 = _nt_no_fold_literal
-
if r2
-
r0 = r2
-
else
-
r3 = _nt_obs_id_right
-
if r3
-
r0 = r3
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
-
4
node_cache[:id_right][start_index] = r0
-
-
4
r0
-
end
-
-
1
module MsgIdDotAtomText0
-
1
def msg_id_domain_text
-
elements[0]
-
end
-
-
end
-
-
1
def _nt_msg_id_dot_atom_text
-
4
start_index = index
-
4
if node_cache[:msg_id_dot_atom_text].has_key?(index)
-
cached = node_cache[:msg_id_dot_atom_text][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
s0, i0 = [], index
-
4
loop do
-
12
i1, s1 = index, []
-
12
r2 = _nt_msg_id_domain_text
-
12
s1 << r2
-
12
if r2
-
8
if has_terminal?(".", false, index)
-
4
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
4
@index += 1
-
else
-
4
terminal_parse_failure(".")
-
4
r4 = nil
-
end
-
8
if r4
-
4
r3 = r4
-
else
-
4
r3 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
8
s1 << r3
-
end
-
12
if s1.last
-
8
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
8
r1.extend(MsgIdDotAtomText0)
-
else
-
4
@index = i1
-
4
r1 = nil
-
end
-
12
if r1
-
8
s0 << r1
-
else
-
4
break
-
end
-
end
-
4
if s0.empty?
-
@index = i0
-
r0 = nil
-
else
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
end
-
-
4
node_cache[:msg_id_dot_atom_text][start_index] = r0
-
-
4
r0
-
end
-
-
1
module MsgIdDomainText0
-
1
def quoted_domain
-
elements[1]
-
end
-
end
-
-
1
module MsgIdDomainText1
-
1
def DQUOTE1
-
elements[0]
-
end
-
-
1
def DQUOTE2
-
elements[3]
-
end
-
end
-
-
1
def _nt_msg_id_domain_text
-
12
start_index = index
-
12
if node_cache[:msg_id_domain_text].has_key?(index)
-
cached = node_cache[:msg_id_domain_text][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
12
i0 = index
-
12
i1, s1 = index, []
-
12
r2 = _nt_DQUOTE
-
12
s1 << r2
-
12
if r2
-
s3, i3 = [], index
-
loop do
-
i4, s4 = index, []
-
r6 = _nt_FWS
-
if r6
-
r5 = r6
-
else
-
r5 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s4 << r5
-
if r5
-
r7 = _nt_quoted_domain
-
s4 << r7
-
end
-
if s4.last
-
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
r4.extend(MsgIdDomainText0)
-
else
-
@index = i4
-
r4 = nil
-
end
-
if r4
-
s3 << r4
-
else
-
break
-
end
-
end
-
if s3.empty?
-
@index = i3
-
r3 = nil
-
else
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
end
-
s1 << r3
-
if r3
-
r9 = _nt_FWS
-
if r9
-
r8 = r9
-
else
-
r8 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s1 << r8
-
if r8
-
r10 = _nt_DQUOTE
-
s1 << r10
-
end
-
end
-
end
-
12
if s1.last
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
r1.extend(MsgIdDomainText1)
-
else
-
12
@index = i1
-
12
r1 = nil
-
end
-
12
if r1
-
r0 = r1
-
else
-
12
s11, i11 = [], index
-
12
loop do
-
80
r12 = _nt_msg_id_atext
-
80
if r12
-
68
s11 << r12
-
else
-
12
break
-
end
-
end
-
12
if s11.empty?
-
4
@index = i11
-
4
r11 = nil
-
else
-
8
r11 = instantiate_node(SyntaxNode,input, i11...index, s11)
-
end
-
12
if r11
-
8
r0 = r11
-
else
-
4
@index = i0
-
4
r0 = nil
-
end
-
end
-
-
12
node_cache[:msg_id_domain_text][start_index] = r0
-
-
12
r0
-
end
-
-
1
def _nt_msg_id_atext
-
80
start_index = index
-
80
if node_cache[:msg_id_atext].has_key?(index)
-
4
cached = node_cache[:msg_id_atext][index]
-
4
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
4
return cached
-
end
-
-
76
i0 = index
-
76
r1 = _nt_ALPHA
-
76
if r1
-
60
r0 = r1
-
else
-
16
r2 = _nt_DIGIT
-
16
if r2
-
r0 = r2
-
else
-
16
if has_terminal?("!", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
16
terminal_parse_failure("!")
-
16
r3 = nil
-
end
-
16
if r3
-
r0 = r3
-
else
-
16
if has_terminal?("#", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
16
terminal_parse_failure("#")
-
16
r4 = nil
-
end
-
16
if r4
-
r0 = r4
-
else
-
16
if has_terminal?("$", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
16
terminal_parse_failure("$")
-
16
r5 = nil
-
end
-
16
if r5
-
r0 = r5
-
else
-
16
if has_terminal?("%", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
16
terminal_parse_failure("%")
-
16
r6 = nil
-
end
-
16
if r6
-
r0 = r6
-
else
-
16
if has_terminal?("&", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
16
terminal_parse_failure("&")
-
16
r7 = nil
-
end
-
16
if r7
-
r0 = r7
-
else
-
16
if has_terminal?("'", false, index)
-
r8 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
16
terminal_parse_failure("'")
-
16
r8 = nil
-
end
-
16
if r8
-
r0 = r8
-
else
-
16
if has_terminal?("*", false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
16
terminal_parse_failure("*")
-
16
r9 = nil
-
end
-
16
if r9
-
r0 = r9
-
else
-
16
if has_terminal?("+", false, index)
-
r10 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
16
terminal_parse_failure("+")
-
16
r10 = nil
-
end
-
16
if r10
-
r0 = r10
-
else
-
16
if has_terminal?("-", false, index)
-
8
r11 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
8
@index += 1
-
else
-
8
terminal_parse_failure("-")
-
8
r11 = nil
-
end
-
16
if r11
-
8
r0 = r11
-
else
-
8
if has_terminal?("/", false, index)
-
r12 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("/")
-
8
r12 = nil
-
end
-
8
if r12
-
r0 = r12
-
else
-
8
if has_terminal?("=", false, index)
-
r13 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("=")
-
8
r13 = nil
-
end
-
8
if r13
-
r0 = r13
-
else
-
8
if has_terminal?("?", false, index)
-
r14 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("?")
-
8
r14 = nil
-
end
-
8
if r14
-
r0 = r14
-
else
-
8
if has_terminal?("^", false, index)
-
r15 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("^")
-
8
r15 = nil
-
end
-
8
if r15
-
r0 = r15
-
else
-
8
if has_terminal?("_", false, index)
-
r16 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("_")
-
8
r16 = nil
-
end
-
8
if r16
-
r0 = r16
-
else
-
8
if has_terminal?("`", false, index)
-
r17 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("`")
-
8
r17 = nil
-
end
-
8
if r17
-
r0 = r17
-
else
-
8
if has_terminal?("{", false, index)
-
r18 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("{")
-
8
r18 = nil
-
end
-
8
if r18
-
r0 = r18
-
else
-
8
if has_terminal?("|", false, index)
-
r19 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("|")
-
8
r19 = nil
-
end
-
8
if r19
-
r0 = r19
-
else
-
8
if has_terminal?("}", false, index)
-
r20 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("}")
-
8
r20 = nil
-
end
-
8
if r20
-
r0 = r20
-
else
-
8
if has_terminal?("~", false, index)
-
r21 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("~")
-
8
r21 = nil
-
end
-
8
if r21
-
r0 = r21
-
else
-
8
if has_terminal?("@", false, index)
-
r22 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
8
terminal_parse_failure("@")
-
8
r22 = nil
-
end
-
8
if r22
-
r0 = r22
-
else
-
8
@index = i0
-
8
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
-
76
node_cache[:msg_id_atext][start_index] = r0
-
-
76
r0
-
end
-
-
1
module NoFoldQuote0
-
1
def DQUOTE1
-
elements[0]
-
end
-
-
1
def DQUOTE2
-
elements[2]
-
end
-
end
-
-
1
def _nt_no_fold_quote
-
start_index = index
-
if node_cache[:no_fold_quote].has_key?(index)
-
cached = node_cache[:no_fold_quote][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_DQUOTE
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
i3 = index
-
r4 = _nt_qtext
-
if r4
-
r3 = r4
-
else
-
r5 = _nt_quoted_pair
-
if r5
-
r3 = r5
-
else
-
@index = i3
-
r3 = nil
-
end
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
if s2.empty?
-
@index = i2
-
r2 = nil
-
else
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
end
-
s0 << r2
-
if r2
-
r6 = _nt_DQUOTE
-
s0 << r6
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(NoFoldQuote0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:no_fold_quote][start_index] = r0
-
-
r0
-
end
-
-
1
module NoFoldLiteral0
-
end
-
-
1
def _nt_no_fold_literal
-
start_index = index
-
if node_cache[:no_fold_literal].has_key?(index)
-
cached = node_cache[:no_fold_literal][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("[", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("[")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
i3 = index
-
r4 = _nt_dtext
-
if r4
-
r3 = r4
-
else
-
r5 = _nt_quoted_pair
-
if r5
-
r3 = r5
-
else
-
@index = i3
-
r3 = nil
-
end
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
if s2.empty?
-
@index = i2
-
r2 = nil
-
else
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
end
-
s0 << r2
-
if r2
-
if has_terminal?("]", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("]")
-
r6 = nil
-
end
-
s0 << r6
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(NoFoldLiteral0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:no_fold_literal][start_index] = r0
-
-
r0
-
end
-
-
end
-
-
1
class RFC2822Parser < Treetop::Runtime::CompiledParser
-
1
include RFC2822
-
end
-
-
end
-
# Autogenerated from a Treetop grammar. Edits may be lost.
-
-
-
1
module Mail
-
1
module RFC2822Obsolete
-
1
include Treetop::Runtime
-
-
1
def root
-
@root ||= :obs_qp
-
end
-
-
1
module ObsQp0
-
end
-
-
1
def _nt_obs_qp
-
1
start_index = index
-
1
if node_cache[:obs_qp].has_key?(index)
-
cached = node_cache[:obs_qp][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
1
i0, s0 = index, []
-
1
if has_terminal?("\\", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
1
terminal_parse_failure("\\")
-
1
r1 = nil
-
end
-
1
s0 << r1
-
1
if r1
-
if has_terminal?('\G[\\x00-\\x7F]', true, index)
-
r2 = true
-
@index += 1
-
else
-
r2 = nil
-
end
-
s0 << r2
-
end
-
1
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsQp0)
-
else
-
1
@index = i0
-
1
r0 = nil
-
end
-
-
1
node_cache[:obs_qp][start_index] = r0
-
-
1
r0
-
end
-
-
1
module ObsText0
-
1
def obs_char
-
elements[0]
-
end
-
-
end
-
-
1
module ObsText1
-
end
-
-
1
def _nt_obs_text
-
start_index = index
-
if node_cache[:obs_text].has_key?(index)
-
cached = node_cache[:obs_text][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
s1, i1 = [], index
-
loop do
-
r2 = _nt_LF
-
if r2
-
s1 << r2
-
else
-
break
-
end
-
end
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
s0 << r1
-
if r1
-
s3, i3 = [], index
-
loop do
-
r4 = _nt_CR
-
if r4
-
s3 << r4
-
else
-
break
-
end
-
end
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
s0 << r3
-
if r3
-
s5, i5 = [], index
-
loop do
-
i6, s6 = index, []
-
r7 = _nt_obs_char
-
s6 << r7
-
if r7
-
s8, i8 = [], index
-
loop do
-
r9 = _nt_LF
-
if r9
-
s8 << r9
-
else
-
break
-
end
-
end
-
r8 = instantiate_node(SyntaxNode,input, i8...index, s8)
-
s6 << r8
-
if r8
-
s10, i10 = [], index
-
loop do
-
r11 = _nt_CR
-
if r11
-
s10 << r11
-
else
-
break
-
end
-
end
-
r10 = instantiate_node(SyntaxNode,input, i10...index, s10)
-
s6 << r10
-
end
-
end
-
if s6.last
-
r6 = instantiate_node(SyntaxNode,input, i6...index, s6)
-
r6.extend(ObsText0)
-
else
-
@index = i6
-
r6 = nil
-
end
-
if r6
-
s5 << r6
-
else
-
break
-
end
-
end
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
s0 << r5
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsText1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_text][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_obs_char
-
start_index = index
-
if node_cache[:obs_char].has_key?(index)
-
cached = node_cache[:obs_char][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
if has_terminal?('\G[\\x00-\\x09]', true, index)
-
r1 = true
-
@index += 1
-
else
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
if has_terminal?('\G[\\x0B-\\x0C]', true, index)
-
r2 = true
-
@index += 1
-
else
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?('\G[\\x0E-\\x7F]', true, index)
-
r3 = true
-
@index += 1
-
else
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
-
node_cache[:obs_char][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_obs_utext
-
start_index = index
-
if node_cache[:obs_utext].has_key?(index)
-
cached = node_cache[:obs_utext][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
r0 = _nt_obs_text
-
-
node_cache[:obs_utext][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_obs_phrase
-
4
start_index = index
-
4
if node_cache[:obs_phrase].has_key?(index)
-
cached = node_cache[:obs_phrase][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
s0, i0 = [], index
-
4
loop do
-
16
i1 = index
-
16
r2 = _nt_word
-
16
if r2
-
8
r1 = r2
-
else
-
8
if has_terminal?(".", false, index)
-
2
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
2
@index += 1
-
else
-
6
terminal_parse_failure(".")
-
6
r3 = nil
-
end
-
8
if r3
-
2
r1 = r3
-
else
-
6
if has_terminal?("@", false, index)
-
2
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
2
@index += 1
-
else
-
4
terminal_parse_failure("@")
-
4
r4 = nil
-
end
-
6
if r4
-
2
r1 = r4
-
else
-
4
@index = i1
-
4
r1 = nil
-
end
-
end
-
end
-
16
if r1
-
12
s0 << r1
-
else
-
4
break
-
end
-
end
-
4
if s0.empty?
-
@index = i0
-
r0 = nil
-
else
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
end
-
-
4
node_cache[:obs_phrase][start_index] = r0
-
-
4
r0
-
end
-
-
1
module ObsPhraseList0
-
end
-
-
1
module ObsPhraseList1
-
end
-
-
1
def _nt_obs_phrase_list
-
start_index = index
-
if node_cache[:obs_phrase_list].has_key?(index)
-
cached = node_cache[:obs_phrase_list][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
r1 = _nt_phrase
-
if r1
-
r0 = r1
-
else
-
i2, s2 = index, []
-
s3, i3 = [], index
-
loop do
-
i4, s4 = index, []
-
r6 = _nt_phrase
-
if r6
-
r5 = r6
-
else
-
r5 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s4 << r5
-
if r5
-
r8 = _nt_CFWS
-
if r8
-
r7 = r8
-
else
-
r7 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s4 << r7
-
if r7
-
if has_terminal?(",", false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r9 = nil
-
end
-
s4 << r9
-
if r9
-
r11 = _nt_CFWS
-
if r11
-
r10 = r11
-
else
-
r10 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s4 << r10
-
end
-
end
-
end
-
if s4.last
-
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
r4.extend(ObsPhraseList0)
-
else
-
@index = i4
-
r4 = nil
-
end
-
if r4
-
s3 << r4
-
else
-
break
-
end
-
end
-
if s3.empty?
-
@index = i3
-
r3 = nil
-
else
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
end
-
s2 << r3
-
if r3
-
r13 = _nt_phrase
-
if r13
-
r12 = r13
-
else
-
r12 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s2 << r12
-
end
-
if s2.last
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
r2.extend(ObsPhraseList1)
-
else
-
@index = i2
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
-
node_cache[:obs_phrase_list][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsFWS0
-
1
def CRLF
-
elements[0]
-
end
-
-
end
-
-
1
module ObsFWS1
-
end
-
-
1
def _nt_obs_FWS
-
114
start_index = index
-
114
if node_cache[:obs_FWS].has_key?(index)
-
cached = node_cache[:obs_FWS][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
114
i0, s0 = index, []
-
114
s1, i1 = [], index
-
114
loop do
-
118
r2 = _nt_WSP
-
118
if r2
-
4
s1 << r2
-
else
-
114
break
-
end
-
end
-
114
if s1.empty?
-
110
@index = i1
-
110
r1 = nil
-
else
-
4
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
end
-
114
s0 << r1
-
114
if r1
-
4
s3, i3 = [], index
-
4
loop do
-
4
i4, s4 = index, []
-
4
r5 = _nt_CRLF
-
4
s4 << r5
-
4
if r5
-
s6, i6 = [], index
-
loop do
-
r7 = _nt_WSP
-
if r7
-
s6 << r7
-
else
-
break
-
end
-
end
-
if s6.empty?
-
@index = i6
-
r6 = nil
-
else
-
r6 = instantiate_node(SyntaxNode,input, i6...index, s6)
-
end
-
s4 << r6
-
end
-
4
if s4.last
-
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
r4.extend(ObsFWS0)
-
else
-
4
@index = i4
-
4
r4 = nil
-
end
-
4
if r4
-
s3 << r4
-
else
-
4
break
-
end
-
end
-
4
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
4
s0 << r3
-
end
-
114
if s0.last
-
4
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
4
r0.extend(ObsFWS1)
-
else
-
110
@index = i0
-
110
r0 = nil
-
end
-
-
114
node_cache[:obs_FWS][start_index] = r0
-
-
114
r0
-
end
-
-
1
module ObsDayOfWeek0
-
1
def day_name
-
elements[1]
-
end
-
-
end
-
-
1
def _nt_obs_day_of_week
-
start_index = index
-
if node_cache[:obs_day_of_week].has_key?(index)
-
cached = node_cache[:obs_day_of_week][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
r3 = _nt_day_name
-
s0 << r3
-
if r3
-
r5 = _nt_CFWS
-
if r5
-
r4 = r5
-
else
-
r4 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r4
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsDayOfWeek0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_day_of_week][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsYear0
-
1
def DIGIT1
-
elements[0]
-
end
-
-
1
def DIGIT2
-
elements[1]
-
end
-
end
-
-
1
module ObsYear1
-
end
-
-
1
def _nt_obs_year
-
start_index = index
-
if node_cache[:obs_year].has_key?(index)
-
cached = node_cache[:obs_year][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
i3, s3 = index, []
-
r4 = _nt_DIGIT
-
s3 << r4
-
if r4
-
r5 = _nt_DIGIT
-
s3 << r5
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(ObsYear0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
s0 << r3
-
if r3
-
r7 = _nt_CFWS
-
if r7
-
r6 = r7
-
else
-
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r6
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsYear1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_year][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsMonth0
-
1
def CFWS1
-
elements[0]
-
end
-
-
1
def month_name
-
elements[1]
-
end
-
-
1
def CFWS2
-
elements[2]
-
end
-
end
-
-
1
def _nt_obs_month
-
start_index = index
-
if node_cache[:obs_month].has_key?(index)
-
cached = node_cache[:obs_month][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_CFWS
-
s0 << r1
-
if r1
-
r2 = _nt_month_name
-
s0 << r2
-
if r2
-
r3 = _nt_CFWS
-
s0 << r3
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsMonth0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_month][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsDay0
-
1
def DIGIT1
-
elements[0]
-
end
-
-
1
def DIGIT2
-
elements[1]
-
end
-
end
-
-
1
module ObsDay1
-
end
-
-
1
def _nt_obs_day
-
start_index = index
-
if node_cache[:obs_day].has_key?(index)
-
cached = node_cache[:obs_day][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
i3 = index
-
r4 = _nt_DIGIT
-
if r4
-
r3 = r4
-
else
-
i5, s5 = index, []
-
r6 = _nt_DIGIT
-
s5 << r6
-
if r6
-
r7 = _nt_DIGIT
-
s5 << r7
-
end
-
if s5.last
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
r5.extend(ObsDay0)
-
else
-
@index = i5
-
r5 = nil
-
end
-
if r5
-
r3 = r5
-
else
-
@index = i3
-
r3 = nil
-
end
-
end
-
s0 << r3
-
if r3
-
r9 = _nt_CFWS
-
if r9
-
r8 = r9
-
else
-
r8 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r8
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsDay1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_day][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsHour0
-
1
def DIGIT1
-
elements[0]
-
end
-
-
1
def DIGIT2
-
elements[1]
-
end
-
end
-
-
1
module ObsHour1
-
end
-
-
1
def _nt_obs_hour
-
start_index = index
-
if node_cache[:obs_hour].has_key?(index)
-
cached = node_cache[:obs_hour][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
i3, s3 = index, []
-
r4 = _nt_DIGIT
-
s3 << r4
-
if r4
-
r5 = _nt_DIGIT
-
s3 << r5
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(ObsHour0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
s0 << r3
-
if r3
-
r7 = _nt_CFWS
-
if r7
-
r6 = r7
-
else
-
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r6
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsHour1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_hour][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsMinute0
-
1
def DIGIT1
-
elements[0]
-
end
-
-
1
def DIGIT2
-
elements[1]
-
end
-
end
-
-
1
module ObsMinute1
-
end
-
-
1
def _nt_obs_minute
-
start_index = index
-
if node_cache[:obs_minute].has_key?(index)
-
cached = node_cache[:obs_minute][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
i3, s3 = index, []
-
r4 = _nt_DIGIT
-
s3 << r4
-
if r4
-
r5 = _nt_DIGIT
-
s3 << r5
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(ObsMinute0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
s0 << r3
-
if r3
-
r7 = _nt_CFWS
-
if r7
-
r6 = r7
-
else
-
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r6
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsMinute1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_minute][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsSecond0
-
1
def DIGIT1
-
elements[0]
-
end
-
-
1
def DIGIT2
-
elements[1]
-
end
-
end
-
-
1
module ObsSecond1
-
end
-
-
1
def _nt_obs_second
-
start_index = index
-
if node_cache[:obs_second].has_key?(index)
-
cached = node_cache[:obs_second][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
i3, s3 = index, []
-
r4 = _nt_DIGIT
-
s3 << r4
-
if r4
-
r5 = _nt_DIGIT
-
s3 << r5
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(ObsSecond0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
s0 << r3
-
if r3
-
r7 = _nt_CFWS
-
if r7
-
r6 = r7
-
else
-
r6 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r6
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsSecond1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_second][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_obs_zone
-
start_index = index
-
if node_cache[:obs_zone].has_key?(index)
-
cached = node_cache[:obs_zone][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0 = index
-
if has_terminal?("UT", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 2))
-
@index += 2
-
else
-
terminal_parse_failure("UT")
-
r1 = nil
-
end
-
if r1
-
r0 = r1
-
else
-
if has_terminal?("GMT", false, index)
-
r2 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("GMT")
-
r2 = nil
-
end
-
if r2
-
r0 = r2
-
else
-
if has_terminal?("EST", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("EST")
-
r3 = nil
-
end
-
if r3
-
r0 = r3
-
else
-
if has_terminal?("EDT", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("EDT")
-
r4 = nil
-
end
-
if r4
-
r0 = r4
-
else
-
if has_terminal?("CST", false, index)
-
r5 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("CST")
-
r5 = nil
-
end
-
if r5
-
r0 = r5
-
else
-
if has_terminal?("CDT", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("CDT")
-
r6 = nil
-
end
-
if r6
-
r0 = r6
-
else
-
if has_terminal?("MST", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("MST")
-
r7 = nil
-
end
-
if r7
-
r0 = r7
-
else
-
if has_terminal?("MDT", false, index)
-
r8 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("MDT")
-
r8 = nil
-
end
-
if r8
-
r0 = r8
-
else
-
if has_terminal?("PST", false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("PST")
-
r9 = nil
-
end
-
if r9
-
r0 = r9
-
else
-
if has_terminal?("PDT", false, index)
-
r10 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("PDT")
-
r10 = nil
-
end
-
if r10
-
r0 = r10
-
else
-
if has_terminal?('\G[\\x41-\\x49]', true, index)
-
r11 = true
-
@index += 1
-
else
-
r11 = nil
-
end
-
if r11
-
r0 = r11
-
else
-
if has_terminal?('\G[\\x4B-\\x5A]', true, index)
-
r12 = true
-
@index += 1
-
else
-
r12 = nil
-
end
-
if r12
-
r0 = r12
-
else
-
if has_terminal?('\G[\\x61-\\x69]', true, index)
-
r13 = true
-
@index += 1
-
else
-
r13 = nil
-
end
-
if r13
-
r0 = r13
-
else
-
if has_terminal?('\G[\\x6B-\\x7A]', true, index)
-
r14 = true
-
@index += 1
-
else
-
r14 = nil
-
end
-
if r14
-
r0 = r14
-
else
-
@index = i0
-
r0 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
-
node_cache[:obs_zone][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsAngleAddr0
-
1
def addr_spec
-
elements[3]
-
end
-
-
end
-
-
1
def _nt_obs_angle_addr
-
4
start_index = index
-
4
if node_cache[:obs_angle_addr].has_key?(index)
-
cached = node_cache[:obs_angle_addr][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
4
i0, s0 = index, []
-
4
r2 = _nt_CFWS
-
4
if r2
-
4
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
4
s0 << r1
-
4
if r1
-
4
if has_terminal?("<", false, index)
-
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
4
terminal_parse_failure("<")
-
4
r3 = nil
-
end
-
4
s0 << r3
-
4
if r3
-
r5 = _nt_obs_route
-
if r5
-
r4 = r5
-
else
-
r4 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r4
-
if r4
-
r6 = _nt_addr_spec
-
s0 << r6
-
if r6
-
if has_terminal?(">", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(">")
-
r7 = nil
-
end
-
s0 << r7
-
if r7
-
r9 = _nt_CFWS
-
if r9
-
r8 = r9
-
else
-
r8 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r8
-
end
-
end
-
end
-
end
-
end
-
4
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsAngleAddr0)
-
else
-
4
@index = i0
-
4
r0 = nil
-
end
-
-
4
node_cache[:obs_angle_addr][start_index] = r0
-
-
4
r0
-
end
-
-
1
module ObsRoute0
-
1
def obs_domain_list
-
elements[1]
-
end
-
-
end
-
-
1
def _nt_obs_route
-
start_index = index
-
if node_cache[:obs_route].has_key?(index)
-
cached = node_cache[:obs_route][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r2 = _nt_CFWS
-
if r2
-
r1 = r2
-
else
-
r1 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r1
-
if r1
-
r3 = _nt_obs_domain_list
-
s0 << r3
-
if r3
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r6 = _nt_CFWS
-
if r6
-
r5 = r6
-
else
-
r5 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r5
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsRoute0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_route][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsDomainList0
-
1
def domain
-
elements[3]
-
end
-
end
-
-
1
module ObsDomainList1
-
1
def domain
-
elements[1]
-
end
-
-
end
-
-
1
def _nt_obs_domain_list
-
start_index = index
-
if node_cache[:obs_domain_list].has_key?(index)
-
cached = node_cache[:obs_domain_list][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("@", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("@")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
r2 = _nt_domain
-
s0 << r2
-
if r2
-
s3, i3 = [], index
-
loop do
-
i4, s4 = index, []
-
s5, i5 = [], index
-
loop do
-
if has_terminal?(",", false, index)
-
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r6 = nil
-
end
-
if r6
-
s5 << r6
-
else
-
break
-
end
-
end
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
s4 << r5
-
if r5
-
r8 = _nt_CFWS
-
if r8
-
r7 = r8
-
else
-
r7 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s4 << r7
-
if r7
-
if has_terminal?("@", false, index)
-
r9 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure("@")
-
r9 = nil
-
end
-
s4 << r9
-
if r9
-
r10 = _nt_domain
-
s4 << r10
-
end
-
end
-
end
-
if s4.last
-
r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
-
r4.extend(ObsDomainList0)
-
else
-
@index = i4
-
r4 = nil
-
end
-
if r4
-
s3 << r4
-
else
-
break
-
end
-
end
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
s0 << r3
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsDomainList1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_domain_list][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsLocalPart0
-
1
def word
-
elements[1]
-
end
-
end
-
-
1
module ObsLocalPart1
-
1
def word
-
elements[0]
-
end
-
-
end
-
-
1
def _nt_obs_local_part
-
start_index = index
-
if node_cache[:obs_local_part].has_key?(index)
-
cached = node_cache[:obs_local_part][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_word
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
i3, s3 = index, []
-
if has_terminal?(".", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(".")
-
r4 = nil
-
end
-
s3 << r4
-
if r4
-
r5 = _nt_word
-
s3 << r5
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(ObsLocalPart0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsLocalPart1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_local_part][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsDomain0
-
1
def atom
-
elements[1]
-
end
-
end
-
-
1
module ObsDomain1
-
1
def atom
-
elements[0]
-
end
-
-
end
-
-
1
def _nt_obs_domain
-
start_index = index
-
if node_cache[:obs_domain].has_key?(index)
-
cached = node_cache[:obs_domain][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_atom
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
i3, s3 = index, []
-
if has_terminal?(".", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(".")
-
r4 = nil
-
end
-
s3 << r4
-
if r4
-
r5 = _nt_atom
-
s3 << r5
-
end
-
if s3.last
-
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
-
r3.extend(ObsDomain0)
-
else
-
@index = i3
-
r3 = nil
-
end
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsDomain1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_domain][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsMboxList0
-
end
-
-
1
module ObsMboxList1
-
end
-
-
1
def _nt_obs_mbox_list
-
start_index = index
-
if node_cache[:obs_mbox_list].has_key?(index)
-
cached = node_cache[:obs_mbox_list][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
s1, i1 = [], index
-
loop do
-
i2, s2 = index, []
-
r4 = _nt_mailbox
-
if r4
-
r3 = r4
-
else
-
r3 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s2 << r3
-
if r3
-
r6 = _nt_CFWS
-
if r6
-
r5 = r6
-
else
-
r5 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s2 << r5
-
if r5
-
if has_terminal?(",", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r7 = nil
-
end
-
s2 << r7
-
if r7
-
r9 = _nt_CFWS
-
if r9
-
r8 = r9
-
else
-
r8 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s2 << r8
-
end
-
end
-
end
-
if s2.last
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
r2.extend(ObsMboxList0)
-
else
-
@index = i2
-
r2 = nil
-
end
-
if r2
-
s1 << r2
-
else
-
break
-
end
-
end
-
if s1.empty?
-
@index = i1
-
r1 = nil
-
else
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
end
-
s0 << r1
-
if r1
-
r11 = _nt_mailbox
-
if r11
-
r10 = r11
-
else
-
r10 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r10
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsMboxList1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_mbox_list][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsAddrList0
-
end
-
-
1
module ObsAddrList1
-
end
-
-
1
def _nt_obs_addr_list
-
start_index = index
-
if node_cache[:obs_addr_list].has_key?(index)
-
cached = node_cache[:obs_addr_list][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
s1, i1 = [], index
-
loop do
-
i2, s2 = index, []
-
r4 = _nt_address
-
if r4
-
r3 = r4
-
else
-
r3 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s2 << r3
-
if r3
-
r6 = _nt_CFWS
-
if r6
-
r5 = r6
-
else
-
r5 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s2 << r5
-
if r5
-
if has_terminal?(",", false, index)
-
r7 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(",")
-
r7 = nil
-
end
-
s2 << r7
-
if r7
-
r9 = _nt_CFWS
-
if r9
-
r8 = r9
-
else
-
r8 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s2 << r8
-
end
-
end
-
end
-
if s2.last
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
r2.extend(ObsAddrList0)
-
else
-
@index = i2
-
r2 = nil
-
end
-
if r2
-
s1 << r2
-
else
-
break
-
end
-
end
-
if s1.empty?
-
@index = i1
-
r1 = nil
-
else
-
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
-
end
-
s0 << r1
-
if r1
-
r11 = _nt_address
-
if r11
-
r10 = r11
-
else
-
r10 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
s0 << r10
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsAddrList1)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_addr_list][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_obs_fields
-
start_index = index
-
if node_cache[:obs_fields].has_key?(index)
-
cached = node_cache[:obs_fields][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
s0, i0 = [], index
-
loop do
-
i1 = index
-
r2 = _nt_obs_return
-
if r2
-
r1 = r2
-
else
-
r3 = _nt_obs_received
-
if r3
-
r1 = r3
-
else
-
r4 = _nt_obs_orig_date
-
if r4
-
r1 = r4
-
else
-
r5 = _nt_obs_from
-
if r5
-
r1 = r5
-
else
-
r6 = _nt_obs_sender
-
if r6
-
r1 = r6
-
else
-
r7 = _nt_obs_reply_to
-
if r7
-
r1 = r7
-
else
-
r8 = _nt_obs_to
-
if r8
-
r1 = r8
-
else
-
r9 = _nt_obs_cc
-
if r9
-
r1 = r9
-
else
-
r10 = _nt_obs_bcc
-
if r10
-
r1 = r10
-
else
-
r11 = _nt_obs_message_id
-
if r11
-
r1 = r11
-
else
-
r12 = _nt_obs_in_reply_to
-
if r12
-
r1 = r12
-
else
-
r13 = _nt_obs_references
-
if r13
-
r1 = r13
-
else
-
r14 = _nt_obs_subject
-
if r14
-
r1 = r14
-
else
-
r15 = _nt_obs_comments
-
if r15
-
r1 = r15
-
else
-
r16 = _nt_obs_keywords
-
if r16
-
r1 = r16
-
else
-
r17 = _nt_obs_resent_date
-
if r17
-
r1 = r17
-
else
-
r18 = _nt_obs_resent_from
-
if r18
-
r1 = r18
-
else
-
r19 = _nt_obs_resent_send
-
if r19
-
r1 = r19
-
else
-
r20 = _nt_obs_resent_rply
-
if r20
-
r1 = r20
-
else
-
r21 = _nt_obs_resent_to
-
if r21
-
r1 = r21
-
else
-
r22 = _nt_obs_resent_cc
-
if r22
-
r1 = r22
-
else
-
r23 = _nt_obs_resent_bcc
-
if r23
-
r1 = r23
-
else
-
r24 = _nt_obs_resent_mid
-
if r24
-
r1 = r24
-
else
-
r25 = _nt_obs_optional
-
if r25
-
r1 = r25
-
else
-
@index = i1
-
r1 = nil
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
if r1
-
s0 << r1
-
else
-
break
-
end
-
end
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
-
node_cache[:obs_fields][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsOrigDate0
-
1
def date_time
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_orig_date
-
start_index = index
-
if node_cache[:obs_orig_date].has_key?(index)
-
cached = node_cache[:obs_orig_date][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Date", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 4))
-
@index += 4
-
else
-
terminal_parse_failure("Date")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_date_time
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsOrigDate0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_orig_date][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsFrom0
-
1
def mailbox_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_from
-
start_index = index
-
if node_cache[:obs_from].has_key?(index)
-
cached = node_cache[:obs_from][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("From", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 4))
-
@index += 4
-
else
-
terminal_parse_failure("From")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_mailbox_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsFrom0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_from][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsSender0
-
1
def mailbox
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_sender
-
start_index = index
-
if node_cache[:obs_sender].has_key?(index)
-
cached = node_cache[:obs_sender][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Sender", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 6))
-
@index += 6
-
else
-
terminal_parse_failure("Sender")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_mailbox
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsSender0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_sender][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsReplyTo0
-
1
def mailbox_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_reply_to
-
start_index = index
-
if node_cache[:obs_reply_to].has_key?(index)
-
cached = node_cache[:obs_reply_to][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Reply-To", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 8))
-
@index += 8
-
else
-
terminal_parse_failure("Reply-To")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_mailbox_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsReplyTo0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_reply_to][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsTo0
-
1
def address_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_to
-
start_index = index
-
if node_cache[:obs_to].has_key?(index)
-
cached = node_cache[:obs_to][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("To", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 2))
-
@index += 2
-
else
-
terminal_parse_failure("To")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_address_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsTo0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_to][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsCc0
-
1
def address_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_cc
-
start_index = index
-
if node_cache[:obs_cc].has_key?(index)
-
cached = node_cache[:obs_cc][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Cc", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 2))
-
@index += 2
-
else
-
terminal_parse_failure("Cc")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_address_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsCc0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_cc][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsBcc0
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_bcc
-
start_index = index
-
if node_cache[:obs_bcc].has_key?(index)
-
cached = node_cache[:obs_bcc][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Bcc", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 3))
-
@index += 3
-
else
-
terminal_parse_failure("Bcc")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
i5 = index
-
r6 = _nt_address_list
-
if r6
-
r5 = r6
-
else
-
r8 = _nt_CFWS
-
if r8
-
r7 = r8
-
else
-
r7 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
if r7
-
r5 = r7
-
else
-
@index = i5
-
r5 = nil
-
end
-
end
-
s0 << r5
-
if r5
-
r9 = _nt_CRLF
-
s0 << r9
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsBcc0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_bcc][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsMessageId0
-
1
def msg_id
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_message_id
-
start_index = index
-
if node_cache[:obs_message_id].has_key?(index)
-
cached = node_cache[:obs_message_id][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Message-ID", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 10))
-
@index += 10
-
else
-
terminal_parse_failure("Message-ID")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_msg_id
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsMessageId0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_message_id][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsInReplyTo0
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_in_reply_to
-
start_index = index
-
if node_cache[:obs_in_reply_to].has_key?(index)
-
cached = node_cache[:obs_in_reply_to][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("In-Reply-To", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 11))
-
@index += 11
-
else
-
terminal_parse_failure("In-Reply-To")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
s5, i5 = [], index
-
loop do
-
i6 = index
-
r7 = _nt_phrase
-
if r7
-
r6 = r7
-
else
-
r8 = _nt_msg_id
-
if r8
-
r6 = r8
-
else
-
@index = i6
-
r6 = nil
-
end
-
end
-
if r6
-
s5 << r6
-
else
-
break
-
end
-
end
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
s0 << r5
-
if r5
-
r9 = _nt_CRLF
-
s0 << r9
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsInReplyTo0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_in_reply_to][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsReferences0
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_references
-
start_index = index
-
if node_cache[:obs_references].has_key?(index)
-
cached = node_cache[:obs_references][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("References", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 10))
-
@index += 10
-
else
-
terminal_parse_failure("References")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
s5, i5 = [], index
-
loop do
-
i6 = index
-
r7 = _nt_phrase
-
if r7
-
r6 = r7
-
else
-
r8 = _nt_msg_id
-
if r8
-
r6 = r8
-
else
-
@index = i6
-
r6 = nil
-
end
-
end
-
if r6
-
s5 << r6
-
else
-
break
-
end
-
end
-
r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
-
s0 << r5
-
if r5
-
r9 = _nt_CRLF
-
s0 << r9
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsReferences0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_references][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_obs_id_left
-
start_index = index
-
if node_cache[:obs_id_left].has_key?(index)
-
cached = node_cache[:obs_id_left][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
r0 = _nt_local_part
-
-
node_cache[:obs_id_left][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_obs_id_right
-
start_index = index
-
if node_cache[:obs_id_right].has_key?(index)
-
cached = node_cache[:obs_id_right][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
r0 = _nt_domain
-
-
node_cache[:obs_id_right][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsSubject0
-
1
def unstructured
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_subject
-
start_index = index
-
if node_cache[:obs_subject].has_key?(index)
-
cached = node_cache[:obs_subject][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Subject", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 7))
-
@index += 7
-
else
-
terminal_parse_failure("Subject")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_unstructured
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsSubject0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_subject][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsComments0
-
1
def unstructured
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_comments
-
start_index = index
-
if node_cache[:obs_comments].has_key?(index)
-
cached = node_cache[:obs_comments][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Comments", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 8))
-
@index += 8
-
else
-
terminal_parse_failure("Comments")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_unstructured
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsComments0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_comments][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsKeywords0
-
1
def obs_phrase_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_keywords
-
start_index = index
-
if node_cache[:obs_keywords].has_key?(index)
-
cached = node_cache[:obs_keywords][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Keywords", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 8))
-
@index += 8
-
else
-
terminal_parse_failure("Keywords")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_obs_phrase_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsKeywords0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_keywords][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsResentFrom0
-
1
def mailbox_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_resent_from
-
start_index = index
-
if node_cache[:obs_resent_from].has_key?(index)
-
cached = node_cache[:obs_resent_from][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Resent-From", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 11))
-
@index += 11
-
else
-
terminal_parse_failure("Resent-From")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_mailbox_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsResentFrom0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_resent_from][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsResentSend0
-
1
def mailbox
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_resent_send
-
start_index = index
-
if node_cache[:obs_resent_send].has_key?(index)
-
cached = node_cache[:obs_resent_send][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Resent-Sender", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 13))
-
@index += 13
-
else
-
terminal_parse_failure("Resent-Sender")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_mailbox
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsResentSend0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_resent_send][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsResentDate0
-
1
def date_time
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_resent_date
-
start_index = index
-
if node_cache[:obs_resent_date].has_key?(index)
-
cached = node_cache[:obs_resent_date][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Resent-Date", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 11))
-
@index += 11
-
else
-
terminal_parse_failure("Resent-Date")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_date_time
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsResentDate0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_resent_date][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsResentTo0
-
1
def address_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_resent_to
-
start_index = index
-
if node_cache[:obs_resent_to].has_key?(index)
-
cached = node_cache[:obs_resent_to][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Resent-To", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 9))
-
@index += 9
-
else
-
terminal_parse_failure("Resent-To")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_address_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsResentTo0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_resent_to][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsResentCc0
-
1
def address_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_resent_cc
-
start_index = index
-
if node_cache[:obs_resent_cc].has_key?(index)
-
cached = node_cache[:obs_resent_cc][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Resent-Cc", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 9))
-
@index += 9
-
else
-
terminal_parse_failure("Resent-Cc")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_address_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsResentCc0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_resent_cc][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsResentBcc0
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_resent_bcc
-
start_index = index
-
if node_cache[:obs_resent_bcc].has_key?(index)
-
cached = node_cache[:obs_resent_bcc][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Resent-Bcc", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 10))
-
@index += 10
-
else
-
terminal_parse_failure("Resent-Bcc")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
i5 = index
-
r6 = _nt_address_list
-
if r6
-
r5 = r6
-
else
-
r8 = _nt_CFWS
-
if r8
-
r7 = r8
-
else
-
r7 = instantiate_node(SyntaxNode,input, index...index)
-
end
-
if r7
-
r5 = r7
-
else
-
@index = i5
-
r5 = nil
-
end
-
end
-
s0 << r5
-
if r5
-
r9 = _nt_CRLF
-
s0 << r9
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsResentBcc0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_resent_bcc][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsResentMid0
-
1
def msg_id
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_resent_mid
-
start_index = index
-
if node_cache[:obs_resent_mid].has_key?(index)
-
cached = node_cache[:obs_resent_mid][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Resent-Message-ID", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 17))
-
@index += 17
-
else
-
terminal_parse_failure("Resent-Message-ID")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_msg_id
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsResentMid0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_resent_mid][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsResentRply0
-
1
def address_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_resent_rply
-
start_index = index
-
if node_cache[:obs_resent_rply].has_key?(index)
-
cached = node_cache[:obs_resent_rply][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Resent-Reply-To", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 15))
-
@index += 15
-
else
-
terminal_parse_failure("Resent-Reply-To")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_address_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsResentRply0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_resent_rply][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsReturn0
-
1
def path
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_return
-
start_index = index
-
if node_cache[:obs_return].has_key?(index)
-
cached = node_cache[:obs_return][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Return-Path", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 11))
-
@index += 11
-
else
-
terminal_parse_failure("Return-Path")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_path
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsReturn0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_return][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsReceived0
-
1
def name_val_list
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_received
-
start_index = index
-
if node_cache[:obs_received].has_key?(index)
-
cached = node_cache[:obs_received][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
if has_terminal?("Received", false, index)
-
r1 = instantiate_node(SyntaxNode,input, index...(index + 8))
-
@index += 8
-
else
-
terminal_parse_failure("Received")
-
r1 = nil
-
end
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_name_val_list
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsReceived0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_received][start_index] = r0
-
-
r0
-
end
-
-
1
def _nt_obs_path
-
start_index = index
-
if node_cache[:obs_path].has_key?(index)
-
cached = node_cache[:obs_path][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
r0 = _nt_obs_angle_addr
-
-
node_cache[:obs_path][start_index] = r0
-
-
r0
-
end
-
-
1
module ObsOptional0
-
1
def field_name
-
elements[0]
-
end
-
-
1
def unstructured
-
elements[3]
-
end
-
-
1
def CRLF
-
elements[4]
-
end
-
end
-
-
1
def _nt_obs_optional
-
start_index = index
-
if node_cache[:obs_optional].has_key?(index)
-
cached = node_cache[:obs_optional][index]
-
if cached
-
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
-
@index = cached.interval.end
-
end
-
return cached
-
end
-
-
i0, s0 = index, []
-
r1 = _nt_field_name
-
s0 << r1
-
if r1
-
s2, i2 = [], index
-
loop do
-
r3 = _nt_WSP
-
if r3
-
s2 << r3
-
else
-
break
-
end
-
end
-
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
-
s0 << r2
-
if r2
-
if has_terminal?(":", false, index)
-
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
-
@index += 1
-
else
-
terminal_parse_failure(":")
-
r4 = nil
-
end
-
s0 << r4
-
if r4
-
r5 = _nt_unstructured
-
s0 << r5
-
if r5
-
r6 = _nt_CRLF
-
s0 << r6
-
end
-
end
-
end
-
end
-
if s0.last
-
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
-
r0.extend(ObsOptional0)
-
else
-
@index = i0
-
r0 = nil
-
end
-
-
node_cache[:obs_optional][start_index] = r0
-
-
r0
-
end
-
-
end
-
-
1
class RFC2822ObsoleteParser < Treetop::Runtime::CompiledParser
-
1
include RFC2822Obsolete
-
end
-
-
end
-
1
module Mail
-
1
class PartsList < Array
-
-
1
def attachments
-
3
Mail::AttachmentsList.new(self)
-
end
-
-
1
def collect
-
4
if block_given?
-
4
ary = PartsList.new
-
8
each { |o| ary << yield(o) }
-
4
ary
-
else
-
to_a
-
end
-
end
-
-
1
undef :map
-
1
alias_method :map, :collect
-
-
1
def map!
-
raise NoMethodError, "#map! is not defined, please call #collect and create a new PartsList"
-
end
-
-
1
def collect!
-
raise NoMethodError, "#collect! is not defined, please call #collect and create a new PartsList"
-
end
-
-
1
def sort
-
10
self.class.new(super)
-
end
-
-
1
def sort!(order)
-
10
sorted = self.sort do |a, b|
-
# OK, 10000 is arbitrary... if anyone actually wants to explicitly sort 10000 parts of a
-
# single email message... please show me a use case and I'll put more work into this method,
-
# in the meantime, it works :)
-
5
get_order_value(a, order) <=> get_order_value(b, order)
-
end
-
10
self.clear
-
20
sorted.each { |p| self << p }
-
end
-
-
1
private
-
-
1
def get_order_value(part, order)
-
10
if part.respond_to?(:content_type)
-
10
order.index(part[:content_type].string.downcase) || 10000
-
else
-
10000
-
end
-
end
-
-
end
-
end
-
# encoding: us-ascii
-
1
module Mail
-
1
module Patterns
-
1
white_space = %Q|\x9\x20|
-
1
text = %Q|\x1-\x8\xB\xC\xE-\x7f|
-
1
field_name = %Q|\x21-\x39\x3b-\x7e|
-
1
qp_safe = %Q|\x20-\x3c\x3e-\x7e|
-
-
1
aspecial = %Q|()<>[]:;@\\,."| # RFC5322
-
1
tspecial = %Q|()<>@,;:\\"/[]?=| # RFC2045
-
1
lwsp = %Q| \t\r\n|
-
1
sp = %Q| |
-
1
control = %Q|\x00-\x1f\x7f-\xff|
-
-
1
if control.respond_to?(:force_encoding)
-
1
control = control.force_encoding(Encoding::BINARY)
-
end
-
-
1
CRLF = /\r\n/
-
1
WSP = /[#{white_space}]/
-
1
FWS = /#{CRLF}#{WSP}*/
-
1
TEXT = /[#{text}]/ # + obs-text
-
1
FIELD_NAME = /[#{field_name}]+/
-
1
FIELD_BODY = /.+/
-
1
FIELD_LINE = /^[#{field_name}]+:\s*.+$/
-
1
HEADER_LINE = /^([#{field_name}]+:\s*.+)$/
-
-
1
QP_UNSAFE = /[^#{qp_safe}]/
-
1
QP_SAFE = /[#{qp_safe}]/
-
1
CONTROL_CHAR = /[#{control}]/n
-
1
ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{sp}]/n
-
1
PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
-
1
TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{sp}]/n
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
module Utilities
-
1
include Patterns
-
-
# Returns true if the string supplied is free from characters not allowed as an ATOM
-
1
def atom_safe?( str )
-
not ATOM_UNSAFE === str
-
end
-
-
# If the string supplied has ATOM unsafe characters in it, will return the string quoted
-
# in double quotes, otherwise returns the string unmodified
-
1
def quote_atom( str )
-
atom_safe?( str ) ? str : dquote(str)
-
end
-
-
# If the string supplied has PHRASE unsafe characters in it, will return the string quoted
-
# in double quotes, otherwise returns the string unmodified
-
1
def quote_phrase( str )
-
4
if RUBY_VERSION >= '1.9'
-
4
original_encoding = str.encoding
-
4
str.force_encoding('ASCII-8BIT')
-
4
if (PHRASE_UNSAFE === str)
-
dquote(str).force_encoding(original_encoding)
-
else
-
4
str.force_encoding(original_encoding)
-
end
-
else
-
(PHRASE_UNSAFE === str) ? dquote(str) : str
-
end
-
end
-
-
# Returns true if the string supplied is free from characters not allowed as a TOKEN
-
1
def token_safe?( str )
-
14
not TOKEN_UNSAFE === str
-
end
-
-
# If the string supplied has TOKEN unsafe characters in it, will return the string quoted
-
# in double quotes, otherwise returns the string unmodified
-
1
def quote_token( str )
-
14
token_safe?( str ) ? str : dquote(str)
-
end
-
-
# Wraps supplied string in double quotes unless it is already wrapped.
-
#
-
# Additionally will escape any double quotation marks in the string with a single
-
# backslash in front of the '"' character.
-
1
def dquote( str )
-
2
match = str.match(/^"(.*)?"$/)
-
2
str = match[1] if match
-
# First remove all escaped double quotes:
-
2
str = str.gsub(/\\"/, '"')
-
# Then wrap and re-escape all double quotes
-
2
'"' + str.gsub(/["]/n) {|s| '\\' + s } + '"'
-
end
-
-
# Unwraps supplied string from inside double quotes.
-
#
-
# Example:
-
#
-
# string = '"This is a string"'
-
# unquote(string) #=> 'This is a string'
-
1
def unquote( str )
-
2
match = str.match(/^"(.*?)"$/)
-
2
match ? match[1] : str
-
end
-
-
# Wraps a string in parenthesis and escapes any that are in the string itself.
-
#
-
# Example:
-
#
-
# paren( 'This is a string' ) #=> '(This is a string)'
-
1
def paren( str )
-
RubyVer.paren( str )
-
end
-
-
# Unwraps a string from being wrapped in parenthesis
-
#
-
# Example:
-
#
-
# str = '(This is a string)'
-
# unparen( str ) #=> 'This is a string'
-
1
def unparen( str )
-
match = str.match(/^\((.*?)\)$/)
-
match ? match[1] : str
-
end
-
-
# Wraps a string in angle brackets and escapes any that are in the string itself
-
#
-
# Example:
-
#
-
# bracket( 'This is a string' ) #=> '<This is a string>'
-
1
def bracket( str )
-
RubyVer.bracket( str )
-
end
-
-
# Unwraps a string from being wrapped in parenthesis
-
#
-
# Example:
-
#
-
# str = '<This is a string>'
-
# unbracket( str ) #=> 'This is a string'
-
1
def unbracket( str )
-
match = str.match(/^\<(.*?)\>$/)
-
match ? match[1] : str
-
end
-
-
# Escape parenthesies in a string
-
#
-
# Example:
-
#
-
# str = 'This is (a) string'
-
# escape_paren( str ) #=> 'This is \(a\) string'
-
1
def escape_paren( str )
-
RubyVer.escape_paren( str )
-
end
-
-
1
def uri_escape( str )
-
uri_parser.escape(str)
-
end
-
-
1
def uri_unescape( str )
-
uri_parser.unescape(str)
-
end
-
-
1
def uri_parser
-
@uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
-
end
-
-
# Matches two objects with their to_s values case insensitively
-
#
-
# Example:
-
#
-
# obj2 = "This_is_An_object"
-
# obj1 = :this_IS_an_object
-
# match_to_s( obj1, obj2 ) #=> true
-
1
def match_to_s( obj1, obj2 )
-
76
obj1.to_s.downcase == obj2.to_s.downcase
-
end
-
-
# Capitalizes a string that is joined by hyphens correctly.
-
#
-
# Example:
-
#
-
# string = 'resent-from-field'
-
# capitalize_field( string ) #=> 'Resent-From-Field'
-
1
def capitalize_field( str )
-
str.to_s.split("-").map { |v| v.capitalize }.join("-")
-
end
-
-
# Takes an underscored word and turns it into a class name
-
#
-
# Example:
-
#
-
# constantize("hello") #=> "Hello"
-
# constantize("hello-there") #=> "HelloThere"
-
# constantize("hello-there-mate") #=> "HelloThereMate"
-
1
def constantize( str )
-
str.to_s.split(/[-_]/).map { |v| v.capitalize }.to_s
-
end
-
-
# Swaps out all underscores (_) for hyphens (-) good for stringing from symbols
-
# a field name.
-
#
-
# Example:
-
#
-
# string = :resent_from_field
-
# dasherize ( string ) #=> 'resent_from_field'
-
1
def dasherize( str )
-
192
str.to_s.gsub('_', '-')
-
end
-
-
# Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
-
# a field name.
-
#
-
# Example:
-
#
-
# string = :resent_from_field
-
# underscoreize ( string ) #=> 'resent_from_field'
-
1
def underscoreize( str )
-
19
str.to_s.downcase.gsub('-', '_')
-
end
-
-
1
if RUBY_VERSION <= '1.8.6'
-
-
def map_lines( str, &block )
-
results = []
-
str.each_line do |line|
-
results << yield(line)
-
end
-
results
-
end
-
-
def map_with_index( enum, &block )
-
results = []
-
enum.each_with_index do |token, i|
-
results[i] = yield(token, i)
-
end
-
results
-
end
-
-
else
-
-
1
def map_lines( str, &block )
-
str.each_line.map(&block)
-
end
-
-
1
def map_with_index( enum, &block )
-
enum.each_with_index.map(&block)
-
end
-
-
end
-
-
end
-
end
-
# encoding: utf-8
-
1
module Mail
-
1
module VERSION
-
-
1
version = {}
-
1
File.read(File.join(File.dirname(__FILE__), '../', 'VERSION')).each_line do |line|
-
4
type, value = line.chomp.split(":")
-
4
next if type =~ /^\s+$/ || value =~ /^\s+$/
-
4
version[type] = value
-
end
-
-
1
MAJOR = version['major']
-
1
MINOR = version['minor']
-
1
PATCH = version['patch']
-
1
BUILD = version['build']
-
-
1
STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
-
-
1
def self.version
-
STRING
-
end
-
-
end
-
end
-
# encoding: utf-8
-
-
1
module Mail
-
1
class Ruby19
-
-
# Escapes any parenthesis in a string that are unescaped this uses
-
# a Ruby 1.9.1 regexp feature of negative look behind
-
1
def Ruby19.escape_paren( str )
-
re = /(?<!\\)([\(\)])/ # Only match unescaped parens
-
str.gsub(re) { |s| '\\' + s }
-
end
-
-
1
def Ruby19.paren( str )
-
str = $1 if str =~ /^\((.*)?\)$/
-
str = escape_paren( str )
-
'(' + str + ')'
-
end
-
-
1
def Ruby19.escape_bracket( str )
-
re = /(?<!\\)([\<\>])/ # Only match unescaped brackets
-
str.gsub(re) { |s| '\\' + s }
-
end
-
-
1
def Ruby19.bracket( str )
-
str = $1 if str =~ /^\<(.*)?\>$/
-
str = escape_bracket( str )
-
'<' + str + '>'
-
end
-
-
1
def Ruby19.decode_base64(str)
-
str.unpack( 'm' ).first
-
end
-
-
1
def Ruby19.encode_base64(str)
-
[str].pack( 'm' )
-
end
-
-
1
def Ruby19.has_constant?(klass, string)
-
klass.const_defined?( string, false )
-
end
-
-
1
def Ruby19.get_constant(klass, string)
-
klass.const_get( string )
-
end
-
-
1
def Ruby19.b_value_encode(str, encoding = nil)
-
encoding = str.encoding.to_s
-
[Ruby19.encode_base64(str), encoding]
-
end
-
-
1
def Ruby19.b_value_decode(str)
-
match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
-
if match
-
encoding = match[1]
-
str = Ruby19.decode_base64(match[2])
-
str.force_encoding(fix_encoding(encoding))
-
end
-
decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
-
decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
-
end
-
-
1
def Ruby19.q_value_encode(str, encoding = nil)
-
encoding = str.encoding.to_s
-
[Encodings::QuotedPrintable.encode(str), encoding]
-
end
-
-
1
def Ruby19.q_value_decode(str)
-
match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m)
-
if match
-
encoding = match[1]
-
str = Encodings::QuotedPrintable.decode(match[2].gsub(/_/, '=20'))
-
str.force_encoding(fix_encoding(encoding))
-
end
-
decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
-
decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
-
end
-
-
1
def Ruby19.param_decode(str, encoding)
-
string = uri_parser.unescape(str)
-
string.force_encoding(encoding) if encoding
-
string
-
end
-
-
1
def Ruby19.param_encode(str)
-
encoding = str.encoding.to_s.downcase
-
language = Configuration.instance.param_encode_language
-
"#{encoding}'#{language}'#{uri_parser.escape(str)}"
-
end
-
-
1
def Ruby19.uri_parser
-
@uri_parser ||= URI::Parser.new
-
end
-
-
# mails somtimes includes invalid encodings like iso885915 or utf8 so we transform them to iso885915 or utf8
-
# TODO: add this as a test somewhere
-
# Encoding.list.map{|e| [e.to_s.upcase==fix_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
-
# Encoding.list.map{|e| [e.to_s==fix_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
-
1
def Ruby19.fix_encoding(encoding)
-
case encoding
-
# ISO-8859-15, ISO-2022-JP and alike
-
when /iso-?(\d{4})-?(\w{1,2})/i then return "ISO-#{$1}-#{$2}"
-
# "ISO-2022-JP-KDDI" and alike
-
when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i then return "ISO-#{$1}-#{$2}-#{$3}"
-
# UTF-8, UTF-32BE and alike
-
when /utf-?(\d{1,2})?(\w{1,2})/i then return "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
-
# Windows-1252 and alike
-
when /Windows-?(.*)/i then return "Windows-#{$1}"
-
#more aliases to be added if needed
-
else return encoding
-
end
-
end
-
end
-
end
-
1
module Metaclass
-
end
-
-
1
require "metaclass/version"
-
1
require "metaclass/object_methods"
-
1
module Metaclass::ObjectMethods
-
1
def __metaclass__
-
6438
class << self
-
6438
self
-
end
-
end
-
end
-
-
1
class Object
-
1
include Metaclass::ObjectMethods
-
end
-
1
module Metaclass
-
1
VERSION = "0.0.1"
-
end
-
# -*- ruby encoding: utf-8 -*-
-
-
# The namespace for MIME applications, tools, and libraries.
-
1
module MIME
-
# Reflects a MIME Content-Type which is in invalid format (e.g., it isn't
-
# in the form of type/subtype).
-
1
class InvalidContentType < RuntimeError; end
-
-
# The definition of one MIME content-type.
-
#
-
# == Usage
-
# require 'mime/types'
-
#
-
# plaintext = MIME::Types['text/plain']
-
# print plaintext.media_type # => 'text'
-
# print plaintext.sub_type # => 'plain'
-
#
-
# puts plaintext.extensions.join(" ") # => 'asc txt c cc h hh cpp'
-
#
-
# puts plaintext.encoding # => 8bit
-
# puts plaintext.binary? # => false
-
# puts plaintext.ascii? # => true
-
# puts plaintext == 'text/plain' # => true
-
# puts MIME::Type.simplified('x-appl/x-zip') # => 'appl/zip'
-
#
-
1
class Type
-
# The released version of Ruby MIME::Types
-
1
VERSION = '1.19'
-
-
1
include Comparable
-
-
1
MEDIA_TYPE_RE = %r{([-\w.+]+)/([-\w.+]*)}o
-
1
UNREG_RE = %r{[Xx]-}o
-
1
ENCODING_RE = %r{(?:base64|7bit|8bit|quoted\-printable)}o
-
1
PLATFORM_RE = %r|#{RUBY_PLATFORM}|o
-
-
1
SIGNATURES = %w(application/pgp-keys application/pgp
-
application/pgp-signature application/pkcs10
-
application/pkcs7-mime application/pkcs7-signature
-
text/vcard)
-
-
1
IANA_URL = "http://www.iana.org/assignments/media-types/%s/%s"
-
1
RFC_URL = "http://rfc-editor.org/rfc/rfc%s.txt"
-
1
DRAFT_URL = "http://datatracker.ietf.org/public/idindex.cgi?command=id_details&filename=%s"
-
1
LTSW_URL = "http://www.ltsw.se/knbase/internet/%s.htp"
-
1
CONTACT_URL = "http://www.iana.org/assignments/contact-people.htm#%s"
-
-
# Returns +true+ if the simplified type matches the current
-
1
def like?(other)
-
if other.respond_to?(:simplified)
-
@simplified == other.simplified
-
else
-
@simplified == Type.simplified(other)
-
end
-
end
-
-
# Compares the MIME::Type against the exact content type or the
-
# simplified type (the simplified type will be used if comparing against
-
# something that can be treated as a String with #to_s). In comparisons,
-
# this is done against the lowercase version of the MIME::Type.
-
1
def <=>(other)
-
69
if other.respond_to?(:content_type)
-
69
@content_type.downcase <=> other.content_type.downcase
-
elsif other.respond_to?(:to_s)
-
@simplified <=> Type.simplified(other.to_s)
-
else
-
@content_type.downcase <=> other.downcase
-
end
-
end
-
-
# Compares the MIME::Type based on how reliable it is before doing a
-
# normal <=> comparison. Used by MIME::Types#[] to sort types. The
-
# comparisons involved are:
-
#
-
# 1. self.simplified <=> other.simplified (ensures that we
-
# don't try to compare different types)
-
# 2. IANA-registered definitions > other definitions.
-
# 3. Generic definitions > platform definitions.
-
# 3. Complete definitions > incomplete definitions.
-
# 4. Current definitions > obsolete definitions.
-
# 5. Obselete with use-instead references > obsolete without.
-
# 6. Obsolete use-instead definitions are compared.
-
1
def priority_compare(other)
-
pc = simplified <=> other.simplified
-
-
if pc.zero? and registered? != other.registered?
-
pc = registered? ? -1 : 1
-
end
-
-
if pc.zero? and platform? != other.platform?
-
pc = platform? ? 1 : -1
-
end
-
-
if pc.zero? and complete? != other.complete?
-
pc = complete? ? -1 : 1
-
end
-
-
if pc.zero? and obsolete? != other.obsolete?
-
pc = obsolete? ? 1 : -1
-
end
-
-
if pc.zero? and obsolete? and (use_instead != other.use_instead)
-
pc = if use_instead.nil?
-
-1
-
elsif other.use_instead.nil?
-
1
-
else
-
use_instead <=> other.use_instead
-
end
-
end
-
-
pc
-
end
-
-
# Returns +true+ if the other object is a MIME::Type and the content
-
# types match.
-
1
def eql?(other)
-
other.kind_of?(MIME::Type) and self == other
-
end
-
-
# Returns the whole MIME content-type string.
-
#
-
# text/plain => text/plain
-
# x-chemical/x-pdb => x-chemical/x-pdb
-
1
attr_reader :content_type
-
# Returns the media type of the simplified MIME type.
-
#
-
# text/plain => text
-
# x-chemical/x-pdb => chemical
-
1
attr_reader :media_type
-
# Returns the media type of the unmodified MIME type.
-
#
-
# text/plain => text
-
# x-chemical/x-pdb => x-chemical
-
1
attr_reader :raw_media_type
-
# Returns the sub-type of the simplified MIME type.
-
#
-
# text/plain => plain
-
# x-chemical/x-pdb => pdb
-
1
attr_reader :sub_type
-
# Returns the media type of the unmodified MIME type.
-
#
-
# text/plain => plain
-
# x-chemical/x-pdb => x-pdb
-
1
attr_reader :raw_sub_type
-
# The MIME types main- and sub-label can both start with <tt>x-</tt>,
-
# which indicates that it is a non-registered name. Of course, after
-
# registration this flag can disappear, adds to the confusing
-
# proliferation of MIME types. The simplified string has the <tt>x-</tt>
-
# removed and are translated to lowercase.
-
#
-
# text/plain => text/plain
-
# x-chemical/x-pdb => chemical/pdb
-
1
attr_reader :simplified
-
# The list of extensions which are known to be used for this MIME::Type.
-
# Non-array values will be coerced into an array with #to_a. Array
-
# values will be flattened and +nil+ values removed.
-
1
attr_accessor :extensions
-
1
remove_method :extensions= ;
-
1
def extensions=(ext) #:nodoc:
-
3066
@extensions = [ext].flatten.compact
-
end
-
-
# The encoding (7bit, 8bit, quoted-printable, or base64) required to
-
# transport the data of this content type safely across a network, which
-
# roughly corresponds to Content-Transfer-Encoding. A value of +nil+ or
-
# <tt>:default</tt> will reset the #encoding to the #default_encoding
-
# for the MIME::Type. Raises ArgumentError if the encoding provided is
-
# invalid.
-
#
-
# If the encoding is not provided on construction, this will be either
-
# 'quoted-printable' (for text/* media types) and 'base64' for eveything
-
# else.
-
1
attr_accessor :encoding
-
1
remove_method :encoding= ;
-
1
def encoding=(enc) #:nodoc:
-
3066
if enc.nil? or enc == :default
-
2913
@encoding = self.default_encoding
-
153
elsif enc =~ ENCODING_RE
-
153
@encoding = enc
-
else
-
raise ArgumentError, "The encoding must be nil, :default, base64, 7bit, 8bit, or quoted-printable."
-
end
-
end
-
-
# The regexp for the operating system that this MIME::Type is specific
-
# to.
-
1
attr_accessor :system
-
1
remove_method :system= ;
-
1
def system=(os) #:nodoc:
-
3066
if os.nil? or os.kind_of?(Regexp)
-
3063
@system = os
-
else
-
3
@system = %r|#{os}|
-
end
-
end
-
# Returns the default encoding for the MIME::Type based on the media
-
# type.
-
1
attr_reader :default_encoding
-
1
remove_method :default_encoding
-
1
def default_encoding
-
2913
(@media_type == 'text') ? 'quoted-printable' : 'base64'
-
end
-
-
# Returns the media type or types that should be used instead of this
-
# media type, if it is obsolete. If there is no replacement media type,
-
# or it is not obsolete, +nil+ will be returned.
-
1
attr_reader :use_instead
-
1
remove_method :use_instead
-
1
def use_instead
-
return nil unless @obsolete
-
@use_instead
-
end
-
-
# Returns +true+ if the media type is obsolete.
-
1
def obsolete?
-
@obsolete ? true : false
-
end
-
# Sets the obsolescence indicator for this media type.
-
1
attr_writer :obsolete
-
-
# The documentation for this MIME::Type. Documentation about media
-
# types will be found on a media type definition as a comment.
-
# Documentation will be found through #docs.
-
1
attr_accessor :docs
-
1
remove_method :docs= ;
-
1
def docs=(d)
-
3066
if d
-
45
a = d.scan(%r{use-instead:#{MEDIA_TYPE_RE}})
-
-
45
if a.empty?
-
2
@use_instead = nil
-
else
-
86
@use_instead = a.map { |el| "#{el[0]}/#{el[1]}" }
-
end
-
end
-
3066
@docs = d
-
end
-
-
# The encoded URL list for this MIME::Type. See #urls for more
-
# information.
-
1
attr_accessor :url
-
# The decoded URL list for this MIME::Type.
-
# The special URL value IANA will be translated into:
-
# http://www.iana.org/assignments/media-types/<mediatype>/<subtype>
-
#
-
# The special URL value RFC### will be translated into:
-
# http://www.rfc-editor.org/rfc/rfc###.txt
-
#
-
# The special URL value DRAFT:name will be translated into:
-
# https://datatracker.ietf.org/public/idindex.cgi?
-
# command=id_detail&filename=<name>
-
#
-
# The special URL value LTSW will be translated into:
-
# http://www.ltsw.se/knbase/internet/<mediatype>.htp
-
#
-
# The special URL value [token] will be translated into:
-
# http://www.iana.org/assignments/contact-people.htm#<token>
-
#
-
# These values will be accessible through #urls, which always returns an
-
# array.
-
1
def urls
-
@url.map do |el|
-
case el
-
when %r{^IANA$}
-
IANA_URL % [ @media_type, @sub_type ]
-
when %r{^RFC(\d+)$}
-
RFC_URL % $1
-
when %r{^DRAFT:(.+)$}
-
DRAFT_URL % $1
-
when %r{^LTSW$}
-
LTSW_URL % @media_type
-
when %r<^\{([^=]+)=([^\]]+)\}>
-
[$1, $2]
-
when %r{^\[([^=]+)=([^\]]+)\]}
-
[$1, CONTACT_URL % $2]
-
when %r{^\[([^\]]+)\]}
-
CONTACT_URL % $1
-
else
-
el
-
end
-
end
-
end
-
-
1
class << self
-
# The MIME types main- and sub-label can both start with <tt>x-</tt>,
-
# which indicates that it is a non-registered name. Of course, after
-
# registration this flag can disappear, adds to the confusing
-
# proliferation of MIME types. The simplified string has the
-
# <tt>x-</tt> removed and are translated to lowercase.
-
1
def simplified(content_type)
-
1533
matchdata = MEDIA_TYPE_RE.match(content_type)
-
-
1533
if matchdata.nil?
-
simplified = nil
-
else
-
1533
media_type = matchdata.captures[0].downcase.gsub(UNREG_RE, '')
-
1533
subtype = matchdata.captures[1].downcase.gsub(UNREG_RE, '')
-
1533
simplified = "#{media_type}/#{subtype}"
-
end
-
1533
simplified
-
end
-
-
# Creates a MIME::Type from an array in the form of:
-
# [type-name, [extensions], encoding, system]
-
#
-
# +extensions+, +encoding+, and +system+ are optional.
-
#
-
# MIME::Type.from_array("application/x-ruby", ['rb'], '8bit')
-
# MIME::Type.from_array(["application/x-ruby", ['rb'], '8bit'])
-
#
-
# These are equivalent to:
-
#
-
# MIME::Type.new('application/x-ruby') do |t|
-
# t.extensions = %w(rb)
-
# t.encoding = '8bit'
-
# end
-
1
def from_array(*args) #:yields MIME::Type.new:
-
# Dereferences the array one level, if necessary.
-
args = args[0] if args[0].kind_of?(Array)
-
-
if args.size.between?(1, 8)
-
m = MIME::Type.new(args[0]) do |t|
-
t.extensions = args[1] if args.size > 1
-
t.encoding = args[2] if args.size > 2
-
t.system = args[3] if args.size > 3
-
t.obsolete = args[4] if args.size > 4
-
t.docs = args[5] if args.size > 5
-
t.url = args[6] if args.size > 6
-
t.registered = args[7] if args.size > 7
-
end
-
yield m if block_given?
-
else
-
raise ArgumentError, "Array provided must contain between one and eight elements."
-
end
-
m
-
end
-
-
# Creates a MIME::Type from a hash. Keys are case-insensitive,
-
# dashes may be replaced with underscores, and the internal Symbol
-
# of the lowercase-underscore version can be used as well. That is,
-
# Content-Type can be provided as content-type, Content_Type,
-
# content_type, or :content_type.
-
#
-
# Known keys are <tt>Content-Type</tt>,
-
# <tt>Content-Transfer-Encoding</tt>, <tt>Extensions</tt>, and
-
# <tt>System</tt>.
-
#
-
# MIME::Type.from_hash('Content-Type' => 'text/x-yaml',
-
# 'Content-Transfer-Encoding' => '8bit',
-
# 'System' => 'linux',
-
# 'Extensions' => ['yaml', 'yml'])
-
#
-
# This is equivalent to:
-
#
-
# MIME::Type.new('text/x-yaml') do |t|
-
# t.encoding = '8bit'
-
# t.system = 'linux'
-
# t.extensions = ['yaml', 'yml']
-
# end
-
1
def from_hash(hash) #:yields MIME::Type.new:
-
type = {}
-
hash.each_pair do |k, v|
-
type[k.to_s.tr('A-Z', 'a-z').gsub(/-/, '_').to_sym] = v
-
end
-
-
m = MIME::Type.new(type[:content_type]) do |t|
-
t.extensions = type[:extensions]
-
t.encoding = type[:content_transfer_encoding]
-
t.system = type[:system]
-
t.obsolete = type[:obsolete]
-
t.docs = type[:docs]
-
t.url = type[:url]
-
t.registered = type[:registered]
-
end
-
-
yield m if block_given?
-
m
-
end
-
-
# Essentially a copy constructor.
-
#
-
# MIME::Type.from_mime_type(plaintext)
-
#
-
# is equivalent to:
-
#
-
# MIME::Type.new(plaintext.content_type.dup) do |t|
-
# t.extensions = plaintext.extensions.dup
-
# t.system = plaintext.system.dup
-
# t.encoding = plaintext.encoding.dup
-
# end
-
1
def from_mime_type(mime_type) #:yields the new MIME::Type:
-
m = MIME::Type.new(mime_type.content_type.dup) do |t|
-
t.extensions = mime_type.extensions.map { |e| e.dup }
-
t.url = mime_type.url && mime_type.url.map { |e| e.dup }
-
-
mime_type.system && t.system = mime_type.system.dup
-
mime_type.encoding && t.encoding = mime_type.encoding.dup
-
-
t.obsolete = mime_type.obsolete?
-
t.registered = mime_type.registered?
-
-
mime_type.docs && t.docs = mime_type.docs.dup
-
-
end
-
-
yield m if block_given?
-
end
-
end
-
-
# Builds a MIME::Type object from the provided MIME Content Type value
-
# (e.g., 'text/plain' or 'applicaton/x-eruby'). The constructed object
-
# is yielded to an optional block for additional configuration, such as
-
# associating extensions and encoding information.
-
1
def initialize(content_type) #:yields self:
-
1533
matchdata = MEDIA_TYPE_RE.match(content_type)
-
-
1533
if matchdata.nil?
-
raise InvalidContentType, "Invalid Content-Type provided ('#{content_type}')"
-
end
-
-
1533
@content_type = content_type
-
1533
@raw_media_type = matchdata.captures[0]
-
1533
@raw_sub_type = matchdata.captures[1]
-
-
1533
@simplified = MIME::Type.simplified(@content_type)
-
1533
matchdata = MEDIA_TYPE_RE.match(@simplified)
-
1533
@media_type = matchdata.captures[0]
-
1533
@sub_type = matchdata.captures[1]
-
-
1533
self.extensions = nil
-
1533
self.encoding = :default
-
1533
self.system = nil
-
1533
self.registered = true
-
1533
self.url = nil
-
1533
self.obsolete = nil
-
1533
self.docs = nil
-
-
1533
yield self if block_given?
-
end
-
-
# MIME content-types which are not regestered by IANA nor defined in
-
# RFCs are required to start with <tt>x-</tt>. This counts as well for
-
# a new media type as well as a new sub-type of an existing media
-
# type. If either the media-type or the content-type begins with
-
# <tt>x-</tt>, this method will return +false+.
-
1
def registered?
-
if (@raw_media_type =~ UNREG_RE) || (@raw_sub_type =~ UNREG_RE)
-
false
-
else
-
@registered
-
end
-
end
-
1
attr_writer :registered #:nodoc:
-
-
# MIME types can be specified to be sent across a network in particular
-
# formats. This method returns +true+ when the MIME type encoding is set
-
# to <tt>base64</tt>.
-
1
def binary?
-
@encoding == 'base64'
-
end
-
-
# MIME types can be specified to be sent across a network in particular
-
# formats. This method returns +false+ when the MIME type encoding is
-
# set to <tt>base64</tt>.
-
1
def ascii?
-
not binary?
-
end
-
-
# Returns +true+ when the simplified MIME type is in the list of known
-
# digital signatures.
-
1
def signature?
-
SIGNATURES.include?(@simplified.downcase)
-
end
-
-
# Returns +true+ if the MIME::Type is specific to an operating system.
-
1
def system?
-
not @system.nil?
-
end
-
-
# Returns +true+ if the MIME::Type is specific to the current operating
-
# system as represented by RUBY_PLATFORM.
-
1
def platform?
-
system? and (RUBY_PLATFORM =~ @system)
-
end
-
-
# Returns +true+ if the MIME::Type specifies an extension list,
-
# indicating that it is a complete MIME::Type.
-
1
def complete?
-
not @extensions.empty?
-
end
-
-
# Returns the MIME type as a string.
-
1
def to_s
-
@content_type
-
end
-
-
# Returns the MIME type as a string for implicit conversions.
-
1
def to_str
-
@content_type
-
end
-
-
# Returns the MIME type as an array suitable for use with
-
# MIME::Type.from_array.
-
1
def to_a
-
[ @content_type, @extensions, @encoding, @system, @obsolete, @docs,
-
@url, registered? ]
-
end
-
-
# Returns the MIME type as an array suitable for use with
-
# MIME::Type.from_hash.
-
1
def to_hash
-
{ 'Content-Type' => @content_type,
-
'Content-Transfer-Encoding' => @encoding,
-
'Extensions' => @extensions,
-
'System' => @system,
-
'Obsolete' => @obsolete,
-
'Docs' => @docs,
-
'URL' => @url,
-
'Registered' => registered?,
-
}
-
end
-
end
-
-
# = MIME::Types
-
# MIME types are used in MIME-compliant communications, as in e-mail or
-
# HTTP traffic, to indicate the type of content which is transmitted.
-
# MIME::Types provides the ability for detailed information about MIME
-
# entities (provided as a set of MIME::Type objects) to be determined and
-
# used programmatically. There are many types defined by RFCs and vendors,
-
# so the list is long but not complete; don't hesitate to ask to add
-
# additional information. This library follows the IANA collection of MIME
-
# types (see below for reference).
-
#
-
# == Description
-
# MIME types are used in MIME entities, as in email or HTTP traffic. It is
-
# useful at times to have information available about MIME types (or,
-
# inversely, about files). A MIME::Type stores the known information about
-
# one MIME type.
-
#
-
# == Usage
-
# require 'mime/types'
-
#
-
# plaintext = MIME::Types['text/plain']
-
# print plaintext.media_type # => 'text'
-
# print plaintext.sub_type # => 'plain'
-
#
-
# puts plaintext.extensions.join(" ") # => 'asc txt c cc h hh cpp'
-
#
-
# puts plaintext.encoding # => 8bit
-
# puts plaintext.binary? # => false
-
# puts plaintext.ascii? # => true
-
# puts plaintext.obsolete? # => false
-
# puts plaintext.registered? # => true
-
# puts plaintext == 'text/plain' # => true
-
# puts MIME::Type.simplified('x-appl/x-zip') # => 'appl/zip'
-
#
-
# This module is built to conform to the MIME types of RFCs 2045 and 2231.
-
# It follows the official IANA registry at
-
# http://www.iana.org/assignments/media-types/ and
-
# ftp://ftp.iana.org/assignments/media-types with some unofficial types
-
# added from the the collection at
-
# http://www.ltsw.se/knbase/internet/mime.htp
-
#
-
# This is originally based on Perl MIME::Types by Mark Overmeer.
-
#
-
# = Author
-
# Copyright:: Copyright 2002–2012 by Austin Ziegler
-
# <austin@rubyforge.org>
-
# Version:: 1.19
-
# Based On:: Perl
-
# MIME::Types[http://search.cpan.org/author/MARKOV/MIME-Types-1.27/MIME/Types.pm],
-
# Copyright 2001–2009 by Mark Overmeer
-
# <mimetypes@overmeer.net>.
-
# Licence:: Ruby's, Perl Artistic, or GPL version 2 (or later)
-
# See Also:: http://www.iana.org/assignments/media-types/
-
# http://www.ltsw.se/knbase/internet/mime.htp
-
#
-
1
class Types
-
# The released version of Ruby MIME::Types
-
1
VERSION = MIME::Type::VERSION
-
-
# The data version.
-
1
attr_reader :data_version
-
-
1
def initialize(data_version = nil)
-
3023
@type_variants = Hash.new { |h, k| h[k] = [] }
-
1135
@extension_index = Hash.new { |h, k| h[k] = [] }
-
25
@data_version = data_version
-
end
-
-
1
def add_type_variant(mime_type) #:nodoc:
-
3066
@type_variants[mime_type.simplified] << mime_type
-
end
-
-
1
def index_extensions(mime_type) #:nodoc:
-
4300
mime_type.extensions.each { |ext| @extension_index[ext] << mime_type }
-
end
-
-
1
def defined_types #:nodoc:
-
24
@type_variants.values.flatten
-
end
-
-
1
@__types__ = self.new(VERSION)
-
-
# Returns a list of MIME::Type objects, which may be empty. The optional
-
# flag parameters are :complete (finds only complete MIME::Type objects)
-
# and :platform (finds only MIME::Types for the current platform). It is
-
# possible for multiple matches to be returned for either type (in the
-
# example below, 'text/plain' returns two values -- one for the general
-
# case, and one for VMS systems.
-
#
-
# puts "\nMIME::Types['text/plain']"
-
# MIME::Types['text/plain'].each { |t| puts t.to_a.join(", ") }
-
#
-
# puts "\nMIME::Types[/^image/, :complete => true]"
-
# MIME::Types[/^image/, :complete => true].each do |t|
-
# puts t.to_a.join(", ")
-
# end
-
#
-
# If multiple type definitions are returned, returns them sorted as
-
# follows:
-
# 1. Complete definitions sort before incomplete ones;
-
# 2. IANA-registered definitions sort before LTSW-recorded
-
# definitions.
-
# 3. Generic definitions sort before platform-specific ones;
-
# 4. Current definitions sort before obsolete ones;
-
# 5. Obsolete definitions with use-instead clauses sort before those
-
# without;
-
# 6. Obsolete definitions use-instead clauses are compared.
-
# 7. Sort on name.
-
1
def [](type_id, flags = {})
-
if type_id.kind_of?(Regexp)
-
matches = []
-
@type_variants.each_key do |k|
-
matches << @type_variants[k] if k =~ type_id
-
end
-
matches.flatten!
-
elsif type_id.kind_of?(MIME::Type)
-
matches = [type_id]
-
else
-
matches = @type_variants[MIME::Type.simplified(type_id)]
-
end
-
-
matches.delete_if { |e| not e.complete? } if flags[:complete]
-
matches.delete_if { |e| not e.platform? } if flags[:platform]
-
-
matches.sort { |a, b| a.priority_compare(b) }
-
end
-
-
# Return the list of MIME::Types which belongs to the file based on its
-
# filename extension. If +platform+ is +true+, then only file types that
-
# are specific to the current platform will be returned.
-
#
-
# This will always return an array.
-
#
-
# puts "MIME::Types.type_for('citydesk.xml')
-
# => [application/xml, text/xml]
-
# puts "MIME::Types.type_for('citydesk.gif')
-
# => [image/gif]
-
1
def type_for(filename, platform = false)
-
ext = filename.chomp.downcase.gsub(/.*\./o, '')
-
list = @extension_index[ext]
-
list.delete_if { |e| not e.platform? } if platform
-
list
-
end
-
-
# A synonym for MIME::Types.type_for
-
1
def of(filename, platform = false)
-
type_for(filename, platform)
-
end
-
-
# Add one or more MIME::Type objects to the set of known types. Each
-
# type should be experimental (e.g., 'application/x-ruby'). If the type
-
# is already known, a warning will be displayed.
-
#
-
# <strong>Please inform the maintainer of this module when registered
-
# types are missing.</strong>
-
1
def add(*types)
-
1581
types.each do |mime_type|
-
3090
if mime_type.kind_of? MIME::Types
-
24
add(*mime_type.defined_types)
-
else
-
3066
if @type_variants.include?(mime_type.simplified)
-
68
if @type_variants[mime_type.simplified].include?(mime_type)
-
2
warn "Type #{mime_type} already registered as a variant of #{mime_type.simplified}." unless defined? MIME::Types::STARTUP
-
end
-
end
-
3066
add_type_variant(mime_type)
-
3066
index_extensions(mime_type)
-
end
-
end
-
end
-
-
1
class << self
-
1
def add_type_variant(mime_type) #:nodoc:
-
@__types__.add_type_variant(mime_type)
-
end
-
-
1
def index_extensions(mime_type) #:nodoc:
-
@__types__.index_extensions(mime_type)
-
end
-
-
# The regular expression used to match a file-based MIME type
-
# definition.
-
1
TEXT_FORMAT_RE = %r{
-
^
-
\s*
-
([*])? # 0: Unregistered?
-
(!)? # 1: Obsolete?
-
(?:(\w+):)? # 2: Platform marker
-
#{MIME::Type::MEDIA_TYPE_RE} # 3,4: Media type
-
(?:\s+@([^\s]+))? # 5: Extensions
-
(?:\s+:(#{MIME::Type::ENCODING_RE}))? # 6: Encoding
-
(?:\s+'(.+))? # 7: URL list
-
(?:\s+=(.+))? # 8: Documentation
-
\s*
-
$
-
}x
-
-
# Build the type list from a file in the format:
-
#
-
# [*][!][os:]mt/st[<ws>@ext][<ws>:enc][<ws>'url-list][<ws>=docs]
-
#
-
# == *
-
# An unofficial MIME type. This should be used if and only if the MIME type
-
# is not properly specified (that is, not under either x-type or
-
# vnd.name.type).
-
#
-
# == !
-
# An obsolete MIME type. May be used with an unofficial MIME type.
-
#
-
# == os:
-
# Platform-specific MIME type definition.
-
#
-
# == mt
-
# The media type.
-
#
-
# == st
-
# The media subtype.
-
#
-
# == <ws>@ext
-
# The list of comma-separated extensions.
-
#
-
# == <ws>:enc
-
# The encoding.
-
#
-
# == <ws>'url-list
-
# The list of comma-separated URLs.
-
#
-
# == <ws>=docs
-
# The documentation string.
-
#
-
# That is, everything except the media type and the subtype is optional. The
-
# more information that's available, though, the richer the values that can
-
# be provided.
-
1
def load_from_file(filename) #:nodoc:
-
24
if defined? ::Encoding
-
48
data = File.open(filename, 'r:UTF-8') { |f| f.read }
-
else
-
data = File.open(filename) { |f| f.read }
-
end
-
24
data = data.split($/)
-
24
mime = MIME::Types.new
-
24
data.each_with_index { |line, index|
-
1533
item = line.chomp.strip.gsub(%r{#.*}o, '')
-
1533
next if item.empty?
-
-
1533
begin
-
1533
m = TEXT_FORMAT_RE.match(item).captures
-
rescue Exception
-
puts "#{filename}:#{index}: Parsing error in MIME type definitions."
-
puts "=> #{line}"
-
raise
-
end
-
-
unregistered, obsolete, platform, mediatype, subtype, extensions,
-
1533
encoding, urls, docs = *m
-
-
1533
extensions &&= extensions.split(/,/)
-
1533
urls &&= urls.split(/,/)
-
-
1533
mime_type = MIME::Type.new("#{mediatype}/#{subtype}") do |t|
-
1533
t.extensions = extensions
-
1533
t.encoding = encoding
-
1533
t.system = platform
-
1533
t.obsolete = obsolete
-
1533
t.registered = false if unregistered
-
1533
t.docs = docs
-
1533
t.url = urls
-
end
-
-
1533
mime.add(mime_type)
-
}
-
24
mime
-
end
-
-
# Returns a list of MIME::Type objects, which may be empty. The
-
# optional flag parameters are :complete (finds only complete
-
# MIME::Type objects) and :platform (finds only MIME::Types for the
-
# current platform). It is possible for multiple matches to be
-
# returned for either type (in the example below, 'text/plain' returns
-
# two values -- one for the general case, and one for VMS systems.
-
#
-
# puts "\nMIME::Types['text/plain']"
-
# MIME::Types['text/plain'].each { |t| puts t.to_a.join(", ") }
-
#
-
# puts "\nMIME::Types[/^image/, :complete => true]"
-
# MIME::Types[/^image/, :complete => true].each do |t|
-
# puts t.to_a.join(", ")
-
# end
-
1
def [](type_id, flags = {})
-
@__types__[type_id, flags]
-
end
-
-
# Return the list of MIME::Types which belongs to the file based on
-
# its filename extension. If +platform+ is +true+, then only file
-
# types that are specific to the current platform will be returned.
-
#
-
# This will always return an array.
-
#
-
# puts "MIME::Types.type_for('citydesk.xml')
-
# => [application/xml, text/xml]
-
# puts "MIME::Types.type_for('citydesk.gif')
-
# => [image/gif]
-
1
def type_for(filename, platform = false)
-
@__types__.type_for(filename, platform)
-
end
-
-
# A synonym for MIME::Types.type_for
-
1
def of(filename, platform = false)
-
@__types__.type_for(filename, platform)
-
end
-
-
# Add one or more MIME::Type objects to the set of known types. Each
-
# type should be experimental (e.g., 'application/x-ruby'). If the
-
# type is already known, a warning will be displayed.
-
#
-
# <strong>Please inform the maintainer of this module when registered
-
# types are missing.</strong>
-
1
def add(*types)
-
24
@__types__.add(*types)
-
end
-
end
-
-
1
files = Dir[File.join(File.dirname(__FILE__), 'types', '*')]
-
1
MIME::Types::STARTUP = true unless $DEBUG
-
25
files.sort.each { |file| add load_from_file(file) }
-
1
remove_const :STARTUP if defined? STARTUP
-
end
-
end
-
-
# vim: ft=ruby
-
1
begin
-
1
require 'rubygems'
-
1
gem 'minitest'
-
rescue Gem::LoadError
-
# do nothing
-
end
-
-
1
require 'minitest/unit'
-
1
require 'minitest/spec'
-
1
require 'minitest/mock'
-
-
1
MiniTest::Unit.autorun
-
1
class MockExpectationError < StandardError # :nodoc:
-
end # omg... worst bug ever. rdoc doesn't allow 1-liners
-
-
##
-
# A simple and clean mock object framework.
-
-
1
module MiniTest
-
-
##
-
# All mock objects are an instance of Mock
-
-
1
class Mock
-
1
alias :__respond_to? :respond_to?
-
-
1
skip_methods = %w(object_id respond_to_missing? inspect === to_s)
-
-
1
instance_methods.each do |m|
-
102
undef_method m unless skip_methods.include?(m.to_s) || m =~ /^__/
-
end
-
-
1
def initialize # :nodoc:
-
@expected_calls = Hash.new { |calls, name| calls[name] = [] }
-
@actual_calls = Hash.new { |calls, name| calls[name] = [] }
-
end
-
-
##
-
# Expect that method +name+ is called, optionally with +args+, and returns
-
# +retval+.
-
#
-
# @mock.expect(:meaning_of_life, 42)
-
# @mock.meaning_of_life # => 42
-
#
-
# @mock.expect(:do_something_with, true, [some_obj, true])
-
# @mock.do_something_with(some_obj, true) # => true
-
#
-
# +args+ is compared to the expected args using case equality (ie, the
-
# '===' operator), allowing for less specific expectations.
-
#
-
# @mock.expect(:uses_any_string, true, [String])
-
# @mock.uses_any_string("foo") # => true
-
# @mock.verify # => true
-
#
-
# @mock.expect(:uses_one_string, true, ["foo"]
-
# @mock.uses_one_string("bar") # => true
-
# @mock.verify # => raises MockExpectationError
-
-
1
def expect(name, retval, args=[])
-
raise ArgumentError, "args must be an array" unless Array === args
-
@expected_calls[name] << { :retval => retval, :args => args }
-
self
-
end
-
-
1
def __call name, data # :nodoc:
-
case data
-
when Hash then
-
"#{name}(#{data[:args].inspect[1..-2]}) => #{data[:retval].inspect}"
-
else
-
data.map { |d| __call name, d }.join ", "
-
end
-
end
-
-
##
-
# Verify that all methods were called as expected. Raises
-
# +MockExpectationError+ if the mock object was not called as
-
# expected.
-
-
1
def verify
-
@expected_calls.each do |name, calls|
-
calls.each do |expected|
-
msg1 = "expected #{__call name, expected}"
-
msg2 = "#{msg1}, got [#{__call name, @actual_calls[name]}]"
-
-
raise MockExpectationError, msg2 if
-
@actual_calls.has_key?(name) and
-
not @actual_calls[name].include?(expected)
-
-
raise MockExpectationError, msg1 unless
-
@actual_calls.has_key?(name) and
-
@actual_calls[name].include?(expected)
-
end
-
end
-
true
-
end
-
-
1
def method_missing(sym, *args) # :nodoc:
-
unless @expected_calls.has_key?(sym) then
-
raise NoMethodError, "unmocked method %p, expected one of %p" %
-
[sym, @expected_calls.keys.sort_by(&:to_s)]
-
end
-
-
index = @actual_calls[sym].length
-
expected_call = @expected_calls[sym][index]
-
-
unless expected_call then
-
raise MockExpectationError, "No more expects available for %p: %p" %
-
[sym, args]
-
end
-
-
expected_args, retval = expected_call[:args], expected_call[:retval]
-
-
if expected_args.size != args.size then
-
raise ArgumentError, "mocked method %p expects %d arguments, got %d" %
-
[sym, expected_args.size, args.size]
-
end
-
-
fully_matched = expected_args.zip(args).all? { |mod, a|
-
mod === a or mod == a
-
}
-
-
unless fully_matched then
-
raise MockExpectationError, "mocked method %p called with unexpected arguments %p" %
-
[sym, args]
-
end
-
-
@actual_calls[sym] << {
-
:retval => retval,
-
:args => expected_args.zip(args).map { |mod, a| mod === a ? mod : a }
-
}
-
-
retval
-
end
-
-
1
def respond_to?(sym, include_private = false) # :nodoc:
-
return true if @expected_calls.has_key?(sym.to_sym)
-
return __respond_to?(sym, include_private)
-
end
-
end
-
end
-
-
1
class Object # :nodoc:
-
-
##
-
# Add a temporary stubbed method replacing +name+ for the duration
-
# of the +block+. If +val_or_callable+ responds to #call, then it
-
# returns the result of calling it, otherwise returns the value
-
# as-is. Cleans up the stub at the end of the +block+.
-
#
-
# def test_stale_eh
-
# obj_under_test = Something.new
-
# refute obj_under_test.stale?
-
#
-
# Time.stub :now, Time.at(0) do
-
# assert obj_under_test.stale?
-
# end
-
# end
-
-
1
def stub name, val_or_callable, &block
-
new_name = "__minitest_stub__#{name}"
-
-
metaclass = class << self; self; end
-
-
if respond_to? name and not methods.map(&:to_s).include? name.to_s then
-
metaclass.send :define_method, name do |*args|
-
super(*args)
-
end
-
end
-
-
metaclass.send :alias_method, new_name, name
-
-
metaclass.send :define_method, name do |*args|
-
if val_or_callable.respond_to? :call then
-
val_or_callable.call(*args)
-
else
-
val_or_callable
-
end
-
end
-
-
yield self
-
ensure
-
metaclass.send :undef_method, name
-
metaclass.send :alias_method, name, new_name
-
metaclass.send :undef_method, new_name
-
end
-
end
-
1
class ParallelEach
-
1
require 'thread'
-
1
include Enumerable
-
-
1
N = (ENV['N'] || 2).to_i
-
-
1
def initialize list
-
1
@queue = Queue.new # *sigh*... the Queue api sucks sooo much...
-
-
1
list.each { |i| @queue << i }
-
3
N.times { @queue << nil }
-
end
-
-
1
def grep pattern
-
self.class.new super
-
end
-
-
1
def each
-
1
threads = N.times.map {
-
2
Thread.new do
-
2
Thread.current.abort_on_exception = true
-
2
while job = @queue.pop
-
yield job
-
end
-
end
-
}
-
1
threads.map(&:join)
-
end
-
end
-
#!/usr/bin/ruby -w
-
-
1
require 'minitest/unit'
-
-
1
class Module # :nodoc:
-
1
def infect_an_assertion meth, new_name, dont_flip = false # :nodoc:
-
# warn "%-22p -> %p %p" % [meth, new_name, dont_flip]
-
29
self.class_eval <<-EOM
-
def #{new_name} *args
-
case
-
when Proc === self then
-
MiniTest::Spec.current.#{meth}(*args, &self)
-
when #{!!dont_flip} then
-
MiniTest::Spec.current.#{meth}(self, *args)
-
else
-
MiniTest::Spec.current.#{meth}(args.first, self, *args[1..-1])
-
end
-
end
-
EOM
-
end
-
-
##
-
# infect_with_assertions has been removed due to excessive clever.
-
# Use infect_an_assertion directly instead.
-
-
1
def infect_with_assertions(pos_prefix, neg_prefix,
-
skip_re,
-
dont_flip_re = /\c0/,
-
map = {})
-
abort "infect_with_assertions is dead. Use infect_an_assertion directly"
-
end
-
end
-
-
1
module Kernel # :nodoc:
-
##
-
# Describe a series of expectations for a given target +desc+.
-
#
-
# TODO: find good tutorial url.
-
#
-
# Defines a test class subclassing from either MiniTest::Spec or
-
# from the surrounding describe's class. The surrounding class may
-
# subclass MiniTest::Spec manually in order to easily share code:
-
#
-
# class MySpec < MiniTest::Spec
-
# # ... shared code ...
-
# end
-
#
-
# class TestStuff < MySpec
-
# it "does stuff" do
-
# # shared code available here
-
# end
-
# describe "inner stuff" do
-
# it "still does stuff" do
-
# # ...and here
-
# end
-
# end
-
# end
-
-
1
def describe desc, additional_desc = nil, &block # :doc:
-
64
stack = MiniTest::Spec.describe_stack
-
64
name = [stack.last, desc, additional_desc].compact.join("::")
-
64
sclas = stack.last || if Class === self && self < MiniTest::Spec then
-
self
-
else
-
28
MiniTest::Spec.spec_type desc
-
end
-
-
64
cls = sclas.create name, desc
-
-
64
stack.push cls
-
64
cls.class_eval(&block)
-
64
stack.pop
-
64
cls
-
end
-
1
private :describe
-
end
-
-
##
-
# MiniTest::Spec -- The faster, better, less-magical spec framework!
-
#
-
# For a list of expectations, see MiniTest::Expectations.
-
-
1
class MiniTest::Spec < MiniTest::Unit::TestCase
-
##
-
# Contains pairs of matchers and Spec classes to be used to
-
# calculate the superclass of a top-level describe. This allows for
-
# automatically customizable spec types.
-
#
-
# See: register_spec_type and spec_type
-
-
1
TYPES = [[//, MiniTest::Spec]]
-
-
##
-
# Register a new type of spec that matches the spec's description.
-
# This method can take either a Regexp and a spec class or a spec
-
# class and a block that takes the description and returns true if
-
# it matches.
-
#
-
# Eg:
-
#
-
# register_spec_type(/Controller$/, MiniTest::Spec::Rails)
-
#
-
# or:
-
#
-
# register_spec_type(MiniTest::Spec::RailsModel) do |desc|
-
# desc.superclass == ActiveRecord::Base
-
# end
-
-
1
def self.register_spec_type(*args, &block)
-
5
if block then
-
2
matcher, klass = block, args.first
-
else
-
3
matcher, klass = *args
-
end
-
5
TYPES.unshift [matcher, klass]
-
end
-
-
##
-
# Figure out the spec class to use based on a spec's description. Eg:
-
#
-
# spec_type("BlahController") # => MiniTest::Spec::Rails
-
-
1
def self.spec_type desc
-
76
TYPES.find { |matcher, klass|
-
240
if matcher.respond_to? :call then
-
40
matcher.call desc
-
else
-
200
matcher === desc.to_s
-
end
-
}.last
-
end
-
-
1
@@describe_stack = []
-
1
def self.describe_stack # :nodoc:
-
64
@@describe_stack
-
end
-
-
##
-
# Returns the children of this spec.
-
-
1
def self.children
-
847
@children ||= []
-
end
-
-
1
def self.nuke_test_methods! # :nodoc:
-
64
self.public_instance_methods.grep(/^test_/).each do |name|
-
self.send :undef_method, name
-
end
-
end
-
-
##
-
# Define a 'before' action. Inherits the way normal methods should.
-
#
-
# NOTE: +type+ is ignored and is only there to make porting easier.
-
#
-
# Equivalent to MiniTest::Unit::TestCase#setup.
-
-
1
def self.before type = nil, &block
-
define_method :setup do
-
super()
-
self.instance_eval(&block)
-
end
-
end
-
-
##
-
# Define an 'after' action. Inherits the way normal methods should.
-
#
-
# NOTE: +type+ is ignored and is only there to make porting easier.
-
#
-
# Equivalent to MiniTest::Unit::TestCase#teardown.
-
-
1
def self.after type = nil, &block
-
define_method :teardown do
-
self.instance_eval(&block)
-
super()
-
end
-
end
-
-
##
-
# Define an expectation with name +desc+. Name gets morphed to a
-
# proper test method name. For some freakish reason, people who
-
# write specs don't like class inheritence, so this goes way out of
-
# its way to make sure that expectations aren't inherited.
-
#
-
# This is also aliased to #specify and doesn't require a +desc+ arg.
-
#
-
# Hint: If you _do_ want inheritence, use minitest/unit. You can mix
-
# and match between assertions and expectations as much as you want.
-
-
1
def self.it desc = "anonymous", &block
-
783
block ||= proc { skip "(no tests defined)" }
-
-
783
@specs ||= 0
-
783
@specs += 1
-
-
783
name = "test_%04d_%s" % [ @specs, desc ]
-
-
783
define_method name, &block
-
-
783
self.children.each do |mod|
-
mod.send :undef_method, name if mod.public_method_defined? name
-
end
-
-
783
name
-
end
-
-
##
-
# Essentially, define an accessor for +name+ with +block+.
-
#
-
# Why use let instead of def? I honestly don't know.
-
-
1
def self.let name, &block
-
define_method name do
-
@_memoized ||= {}
-
@_memoized.fetch(name) { |k| @_memoized[k] = instance_eval(&block) }
-
end
-
end
-
-
##
-
# Another lazy man's accessor generator. Made even more lazy by
-
# setting the name for you to +subject+.
-
-
1
def self.subject &block
-
let :subject, &block
-
end
-
-
1
def self.create name, desc # :nodoc:
-
64
cls = Class.new(self) do
-
64
@name = name
-
64
@desc = desc
-
-
64
nuke_test_methods!
-
end
-
-
64
children << cls
-
-
64
cls
-
end
-
-
1
def self.to_s # :nodoc:
-
9054
defined?(@name) ? @name : super
-
end
-
-
# :stopdoc:
-
1
class << self
-
1
attr_reader :desc
-
1
alias :specify :it
-
1
alias :name :to_s
-
end
-
# :startdoc:
-
end
-
-
##
-
# It's where you hide your "assertions".
-
-
1
module MiniTest::Expectations
-
##
-
# See MiniTest::Assertions#assert_empty.
-
#
-
# collection.must_be_empty
-
#
-
# :method: must_be_empty
-
-
1
infect_an_assertion :assert_empty, :must_be_empty, :unary
-
-
##
-
# See MiniTest::Assertions#assert_equal
-
#
-
# a.must_equal b
-
#
-
# :method: must_equal
-
-
1
infect_an_assertion :assert_equal, :must_equal
-
-
##
-
# See MiniTest::Assertions#assert_in_delta
-
#
-
# n.must_be_close_to m [, delta]
-
#
-
# :method: must_be_close_to
-
-
1
infect_an_assertion :assert_in_delta, :must_be_close_to
-
-
1
alias :must_be_within_delta :must_be_close_to # :nodoc:
-
-
##
-
# See MiniTest::Assertions#assert_in_epsilon
-
#
-
# n.must_be_within_epsilon m [, epsilon]
-
#
-
# :method: must_be_within_epsilon
-
-
1
infect_an_assertion :assert_in_epsilon, :must_be_within_epsilon
-
-
##
-
# See MiniTest::Assertions#assert_includes
-
#
-
# collection.must_include obj
-
#
-
# :method: must_include
-
-
1
infect_an_assertion :assert_includes, :must_include, :reverse
-
-
##
-
# See MiniTest::Assertions#assert_instance_of
-
#
-
# obj.must_be_instance_of klass
-
#
-
# :method: must_be_instance_of
-
-
1
infect_an_assertion :assert_instance_of, :must_be_instance_of
-
-
##
-
# See MiniTest::Assertions#assert_kind_of
-
#
-
# obj.must_be_kind_of mod
-
#
-
# :method: must_be_kind_of
-
-
1
infect_an_assertion :assert_kind_of, :must_be_kind_of
-
-
##
-
# See MiniTest::Assertions#assert_match
-
#
-
# a.must_match b
-
#
-
# :method: must_match
-
-
1
infect_an_assertion :assert_match, :must_match
-
-
##
-
# See MiniTest::Assertions#assert_nil
-
#
-
# obj.must_be_nil
-
#
-
# :method: must_be_nil
-
-
1
infect_an_assertion :assert_nil, :must_be_nil, :unary
-
-
##
-
# See MiniTest::Assertions#assert_operator
-
#
-
# n.must_be :<=, 42
-
#
-
# This can also do predicates:
-
#
-
# str.must_be :empty?
-
#
-
# :method: must_be
-
-
1
infect_an_assertion :assert_operator, :must_be, :reverse
-
-
##
-
# See MiniTest::Assertions#assert_output
-
#
-
# proc { ... }.must_output out_or_nil [, err]
-
#
-
# :method: must_output
-
-
1
infect_an_assertion :assert_output, :must_output
-
-
##
-
# See MiniTest::Assertions#assert_raises
-
#
-
# proc { ... }.must_raise exception
-
#
-
# :method: must_raise
-
-
1
infect_an_assertion :assert_raises, :must_raise
-
-
##
-
# See MiniTest::Assertions#assert_respond_to
-
#
-
# obj.must_respond_to msg
-
#
-
# :method: must_respond_to
-
-
1
infect_an_assertion :assert_respond_to, :must_respond_to, :reverse
-
-
##
-
# See MiniTest::Assertions#assert_same
-
#
-
# a.must_be_same_as b
-
#
-
# :method: must_be_same_as
-
-
1
infect_an_assertion :assert_same, :must_be_same_as
-
-
##
-
# See MiniTest::Assertions#assert_send
-
# TODO: remove me
-
#
-
# a.must_send
-
#
-
# :method: must_send
-
-
1
infect_an_assertion :assert_send, :must_send
-
-
##
-
# See MiniTest::Assertions#assert_silent
-
#
-
# proc { ... }.must_be_silent
-
#
-
# :method: must_be_silent
-
-
1
infect_an_assertion :assert_silent, :must_be_silent
-
-
##
-
# See MiniTest::Assertions#assert_throws
-
#
-
# proc { ... }.must_throw sym
-
#
-
# :method: must_throw
-
-
1
infect_an_assertion :assert_throws, :must_throw
-
-
##
-
# See MiniTest::Assertions#refute_empty
-
#
-
# collection.wont_be_empty
-
#
-
# :method: wont_be_empty
-
-
1
infect_an_assertion :refute_empty, :wont_be_empty, :unary
-
-
##
-
# See MiniTest::Assertions#refute_equal
-
#
-
# a.wont_equal b
-
#
-
# :method: wont_equal
-
-
1
infect_an_assertion :refute_equal, :wont_equal
-
-
##
-
# See MiniTest::Assertions#refute_in_delta
-
#
-
# n.wont_be_close_to m [, delta]
-
#
-
# :method: wont_be_close_to
-
-
1
infect_an_assertion :refute_in_delta, :wont_be_close_to
-
-
1
alias :wont_be_within_delta :wont_be_close_to # :nodoc:
-
-
##
-
# See MiniTest::Assertions#refute_in_epsilon
-
#
-
# n.wont_be_within_epsilon m [, epsilon]
-
#
-
# :method: wont_be_within_epsilon
-
-
1
infect_an_assertion :refute_in_epsilon, :wont_be_within_epsilon
-
-
##
-
# See MiniTest::Assertions#refute_includes
-
#
-
# collection.wont_include obj
-
#
-
# :method: wont_include
-
-
1
infect_an_assertion :refute_includes, :wont_include, :reverse
-
-
##
-
# See MiniTest::Assertions#refute_instance_of
-
#
-
# obj.wont_be_instance_of klass
-
#
-
# :method: wont_be_instance_of
-
-
1
infect_an_assertion :refute_instance_of, :wont_be_instance_of
-
-
##
-
# See MiniTest::Assertions#refute_kind_of
-
#
-
# obj.wont_be_kind_of mod
-
#
-
# :method: wont_be_kind_of
-
-
1
infect_an_assertion :refute_kind_of, :wont_be_kind_of
-
-
##
-
# See MiniTest::Assertions#refute_match
-
#
-
# a.wont_match b
-
#
-
# :method: wont_match
-
-
1
infect_an_assertion :refute_match, :wont_match
-
-
##
-
# See MiniTest::Assertions#refute_nil
-
#
-
# obj.wont_be_nil
-
#
-
# :method: wont_be_nil
-
-
1
infect_an_assertion :refute_nil, :wont_be_nil, :unary
-
-
##
-
# See MiniTest::Assertions#refute_operator
-
#
-
# n.wont_be :<=, 42
-
#
-
# This can also do predicates:
-
#
-
# str.wont_be :empty?
-
#
-
# :method: wont_be
-
-
1
infect_an_assertion :refute_operator, :wont_be, :reverse
-
-
##
-
# See MiniTest::Assertions#refute_respond_to
-
#
-
# obj.wont_respond_to msg
-
#
-
# :method: wont_respond_to
-
-
1
infect_an_assertion :refute_respond_to, :wont_respond_to, :reverse
-
-
##
-
# See MiniTest::Assertions#refute_same
-
#
-
# a.wont_be_same_as b
-
#
-
# :method: wont_be_same_as
-
-
1
infect_an_assertion :refute_same, :wont_be_same_as
-
end
-
-
1
class Object # :nodoc:
-
1
include MiniTest::Expectations
-
end
-
1
require 'optparse'
-
1
require 'rbconfig'
-
1
require 'thread' # required for 1.8
-
1
require 'minitest/parallel_each'
-
-
##
-
# Minimal (mostly drop-in) replacement for test-unit.
-
#
-
# :include: README.txt
-
-
1
module MiniTest
-
-
1
def self.const_missing name # :nodoc:
-
case name
-
when :MINI_DIR then
-
msg = "MiniTest::MINI_DIR was removed. Don't violate other's internals."
-
warn "WAR\NING: #{msg}"
-
warn "WAR\NING: Used by #{caller.first}."
-
const_set :MINI_DIR, "bad value"
-
else
-
super
-
end
-
end
-
-
##
-
# Assertion base class
-
-
1
class Assertion < Exception; end
-
-
##
-
# Assertion raised when skipping a test
-
-
1
class Skip < Assertion; end
-
-
1
class << self
-
1
attr_accessor :backtrace_filter
-
end
-
-
1
class BacktraceFilter # :nodoc:
-
1
def filter bt
-
441
return ["No backtrace"] unless bt
-
-
441
new_bt = []
-
-
441
unless $DEBUG then
-
441
bt.each do |line|
-
4183
break if line =~ /lib\/minitest/
-
3742
new_bt << line
-
end
-
-
1564
new_bt = bt.reject { |line| line =~ /lib\/minitest/ } if new_bt.empty?
-
441
new_bt = bt.dup if new_bt.empty?
-
else
-
new_bt = bt.dup
-
end
-
-
441
new_bt
-
end
-
end
-
-
1
self.backtrace_filter = BacktraceFilter.new
-
-
1
def self.filter_backtrace bt # :nodoc:
-
441
backtrace_filter.filter bt
-
end
-
-
##
-
# MiniTest Assertions. All assertion methods accept a +msg+ which is
-
# printed if the assertion fails.
-
-
1
module Assertions
-
1
UNDEFINED = Object.new # :nodoc:
-
-
1
def UNDEFINED.inspect # :nodoc:
-
"UNDEFINED" # again with the rdoc bugs... :(
-
end
-
-
##
-
# Returns the diff command to use in #diff. Tries to intelligently
-
# figure out what diff to use.
-
-
1
def self.diff
-
@diff = if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ then
-
"diff.exe -u"
-
else
-
1
if system("gdiff", __FILE__, __FILE__)
-
"gdiff -u" # solaris and kin suck
-
elsif system("diff", __FILE__, __FILE__)
-
1
"diff -u"
-
else
-
nil
-
end
-
3321
end unless defined? @diff
-
-
3321
@diff
-
end
-
-
##
-
# Set the diff command to use in #diff.
-
-
1
def self.diff= o
-
@diff = o
-
end
-
-
##
-
# Returns a diff between +exp+ and +act+. If there is no known
-
# diff command or if it doesn't make sense to diff the output
-
# (single line, short output), then it simply returns a basic
-
# comparison between the two.
-
-
1
def diff exp, act
-
1668
require "tempfile"
-
-
1668
expect = mu_pp_for_diff exp
-
1668
butwas = mu_pp_for_diff act
-
1668
result = nil
-
-
1668
need_to_diff =
-
MiniTest::Assertions.diff &&
-
(expect.include?("\n") ||
-
1668
butwas.include?("\n") ||
-
expect.size > 30 ||
-
butwas.size > 30 ||
-
expect == butwas)
-
-
return "Expected: #{mu_pp exp}\n Actual: #{mu_pp act}" unless
-
1668
need_to_diff
-
-
1653
Tempfile.open("expect") do |a|
-
1653
a.puts expect
-
1653
a.flush
-
-
1653
Tempfile.open("butwas") do |b|
-
1653
b.puts butwas
-
1653
b.flush
-
-
1653
result = `#{MiniTest::Assertions.diff} #{a.path} #{b.path}`
-
1653
result.sub!(/^\-\-\- .+/, "--- expected")
-
1653
result.sub!(/^\+\+\+ .+/, "+++ actual")
-
-
1653
if result.empty? then
-
3
klass = exp.class
-
3
result = [
-
"No visible difference in the #{klass}#inspect output.",
-
"You should look at your implementation of #{klass}#==.",
-
expect
-
].join "\n"
-
end
-
end
-
end
-
-
1653
result
-
end
-
-
##
-
# This returns a human-readable version of +obj+. By default
-
# #inspect is called. You can override this to use #pretty_print
-
# if you want.
-
-
1
def mu_pp obj
-
3853
s = obj.inspect
-
3853
s = s.encode Encoding.default_external if defined? Encoding
-
3853
s
-
end
-
-
##
-
# This returns a diff-able human-readable version of +obj+. This
-
# differs from the regular mu_pp because it expands escaped
-
# newlines and makes hex-values generic (like object_ids). This
-
# uses mu_pp to do the first pass and then cleans it up.
-
-
1
def mu_pp_for_diff obj # TODO: possibly rename
-
3336
mu_pp(obj).gsub(/\\n/, "\n").gsub(/0x[a-f0-9]+/m, '0xXXXXXX')
-
end
-
-
1
def _assertions= n # :nodoc:
-
20842
@_assertions = n
-
end
-
-
1
def _assertions # :nodoc:
-
24790
@_assertions ||= 0
-
end
-
-
##
-
# Fails unless +test+ is a true value.
-
-
1
def assert test, msg = nil
-
16894
msg ||= "Failed assertion, no message given."
-
16894
self._assertions += 1
-
16894
unless test then
-
62
msg = msg.call if Proc === msg
-
62
raise MiniTest::Assertion, msg
-
end
-
16832
true
-
end
-
-
##
-
# Fails unless the block returns a true value.
-
#
-
# NOTE: This method is deprecated, use assert. It will be removed
-
# on 2013-01-01."
-
-
1
def assert_block msg = nil
-
warn "NOTE: MiniTest::Unit::TestCase#assert_block is deprecated, use assert. It will be removed on 2013-01-01. Called from #{caller.first}"
-
msg = message(msg) { "Expected block to return true value" }
-
assert yield, msg
-
end
-
-
##
-
# Fails unless +obj+ is empty.
-
-
1
def assert_empty obj, msg = nil
-
8
msg = message(msg) { "Expected #{mu_pp(obj)} to be empty" }
-
8
assert_respond_to obj, :empty?
-
8
assert obj.empty?, msg
-
end
-
-
##
-
# Fails unless <tt>exp == act</tt> printing the difference between
-
# the two, if possible.
-
#
-
# If there is no visible difference but the assertion fails, you
-
# should suspect that your #== is buggy, or your inspect output is
-
# missing crucial details.
-
#
-
# For floats use assert_in_delta.
-
#
-
# See also: MiniTest::Assertions.diff
-
-
1
def assert_equal exp, act, msg = nil
-
13974
msg = message(msg, "") { diff exp, act }
-
13948
assert(exp == act, msg)
-
end
-
-
##
-
# For comparing Floats. Fails unless +exp+ and +act+ are within +delta+
-
# of each other.
-
#
-
# assert_in_delta Math::PI, (22.0 / 7.0), 0.01
-
-
1
def assert_in_delta exp, act, delta = 0.001, msg = nil
-
n = (exp - act).abs
-
msg = message(msg) { "Expected |#{exp} - #{act}| (#{n}) to be < #{delta}"}
-
assert delta >= n, msg
-
end
-
-
##
-
# For comparing Floats. Fails unless +exp+ and +act+ have a relative
-
# error less than +epsilon+.
-
-
1
def assert_in_epsilon a, b, epsilon = 0.001, msg = nil
-
assert_in_delta a, b, [a.abs, b.abs].min * epsilon, msg
-
end
-
-
##
-
# Fails unless +collection+ includes +obj+.
-
-
1
def assert_includes collection, obj, msg = nil
-
13
msg = message(msg) {
-
1
"Expected #{mu_pp(collection)} to include #{mu_pp(obj)}"
-
}
-
13
assert_respond_to collection, :include?
-
13
assert collection.include?(obj), msg
-
end
-
-
##
-
# Fails unless +obj+ is an instance of +cls+.
-
-
1
def assert_instance_of cls, obj, msg = nil
-
2
msg = message(msg) {
-
"Expected #{mu_pp(obj)} to be an instance of #{cls}, not #{obj.class}"
-
}
-
-
2
assert obj.instance_of?(cls), msg
-
end
-
-
##
-
# Fails unless +obj+ is a kind of +cls+.
-
-
1
def assert_kind_of cls, obj, msg = nil # TODO: merge with instance_of
-
42
msg = message(msg) {
-
"Expected #{mu_pp(obj)} to be a kind of #{cls}, not #{obj.class}" }
-
-
42
assert obj.kind_of?(cls), msg
-
end
-
-
##
-
# Fails unless +matcher+ <tt>=~</tt> +obj+.
-
-
1
def assert_match matcher, obj, msg = nil
-
170
msg = message(msg) { "Expected #{mu_pp matcher} to match #{mu_pp obj}" }
-
170
assert_respond_to matcher, :"=~"
-
170
matcher = Regexp.new Regexp.escape matcher if String === matcher
-
170
assert matcher =~ obj, msg
-
end
-
-
##
-
# Fails unless +obj+ is nil
-
-
1
def assert_nil obj, msg = nil
-
153
msg = message(msg) { "Expected #{mu_pp(obj)} to be nil" }
-
153
assert obj.nil?, msg
-
end
-
-
##
-
# For testing with binary operators.
-
#
-
# assert_operator 5, :<=, 4
-
-
1
def assert_operator o1, op, o2 = UNDEFINED, msg = nil
-
215
return assert_predicate o1, op, msg if UNDEFINED == o2
-
238
msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op} #{mu_pp(o2)}" }
-
215
assert o1.__send__(op, o2), msg
-
end
-
-
##
-
# Fails if stdout or stderr do not output the expected results.
-
# Pass in nil if you don't care about that streams output. Pass in
-
# "" if you require it to be silent. Pass in a regexp if you want
-
# to pattern match.
-
#
-
# NOTE: this uses #capture_io, not #capture_subprocess_io.
-
#
-
# See also: #assert_silent
-
-
1
def assert_output stdout = nil, stderr = nil
-
out, err = capture_io do
-
yield
-
end
-
-
err_msg = Regexp === stderr ? :assert_match : :assert_equal if stderr
-
out_msg = Regexp === stdout ? :assert_match : :assert_equal if stdout
-
-
y = send err_msg, stderr, err, "In stderr" if err_msg
-
x = send out_msg, stdout, out, "In stdout" if out_msg
-
-
(!stdout || x) && (!stderr || y)
-
end
-
-
##
-
# For testing with predicates.
-
#
-
# assert_predicate str, :empty?
-
#
-
# This is really meant for specs and is front-ended by assert_operator:
-
#
-
# str.must_be :empty?
-
-
1
def assert_predicate o1, op, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op}" }
-
assert o1.__send__(op), msg
-
end
-
-
##
-
# Fails unless the block raises one of +exp+. Returns the
-
# exception matched so you can check the message, attributes, etc.
-
-
1
def assert_raises *exp
-
439
msg = "#{exp.pop}.\n" if String === exp.last
-
-
439
should_raise = false
-
439
begin
-
439
yield
-
should_raise = true
-
rescue MiniTest::Skip => e
-
details = "#{msg}#{mu_pp(exp)} exception expected, not"
-
-
if exp.include? MiniTest::Skip then
-
return e
-
else
-
raise e
-
end
-
rescue Exception => e
-
439
details = "#{msg}#{mu_pp(exp)} exception expected, not"
-
assert(exp.any? { |ex|
-
439
ex.instance_of?(Module) ? e.kind_of?(ex) : ex == e.class
-
439
}, exception_details(e, details))
-
-
439
return e
-
end
-
-
exp = exp.first if exp.size == 1
-
flunk "#{msg}#{mu_pp(exp)} expected but nothing was raised." if
-
should_raise
-
end
-
-
##
-
# Fails unless +obj+ responds to +meth+.
-
-
1
def assert_respond_to obj, meth, msg = nil
-
221
msg = message(msg) {
-
"Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}"
-
}
-
221
assert obj.respond_to?(meth), msg
-
end
-
-
##
-
# Fails unless +exp+ and +act+ are #equal?
-
-
1
def assert_same exp, act, msg = nil
-
2
msg = message(msg) {
-
data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id]
-
"Expected %s (oid=%d) to be the same as %s (oid=%d)" % data
-
}
-
2
assert exp.equal?(act), msg
-
end
-
-
##
-
# +send_ary+ is a receiver, message and arguments.
-
#
-
# Fails unless the call returns a true value
-
# TODO: I should prolly remove this from specs
-
-
1
def assert_send send_ary, m = nil
-
recv, msg, *args = send_ary
-
m = message(m) {
-
"Expected #{mu_pp(recv)}.#{msg}(*#{mu_pp(args)}) to return true" }
-
assert recv.__send__(msg, *args), m
-
end
-
-
##
-
# Fails if the block outputs anything to stderr or stdout.
-
#
-
# See also: #assert_output
-
-
1
def assert_silent
-
assert_output "", "" do
-
yield
-
end
-
end
-
-
##
-
# Fails unless the block throws +sym+
-
-
1
def assert_throws sym, msg = nil
-
default = "Expected #{mu_pp(sym)} to have been thrown"
-
caught = true
-
catch(sym) do
-
begin
-
yield
-
rescue ThreadError => e # wtf?!? 1.8 + threads == suck
-
default += ", not :#{e.message[/uncaught throw \`(\w+?)\'/, 1]}"
-
rescue ArgumentError => e # 1.9 exception
-
default += ", not #{e.message.split(/ /).last}"
-
rescue NameError => e # 1.8 exception
-
default += ", not #{e.name.inspect}"
-
end
-
caught = false
-
end
-
-
assert caught, message(msg) { default }
-
end
-
-
##
-
# Captures $stdout and $stderr into strings:
-
#
-
# out, err = capture_io do
-
# puts "Some info"
-
# warn "You did a bad thing"
-
# end
-
#
-
# assert_match %r%info%, out
-
# assert_match %r%bad%, err
-
#
-
# NOTE: For efficiency, this method uses StringIO and does not
-
# capture IO for subprocesses. Use #capture_subprocess_io for
-
# that.
-
-
1
def capture_io
-
require 'stringio'
-
-
captured_stdout, captured_stderr = StringIO.new, StringIO.new
-
-
synchronize do
-
orig_stdout, orig_stderr = $stdout, $stderr
-
$stdout, $stderr = captured_stdout, captured_stderr
-
-
begin
-
yield
-
ensure
-
$stdout = orig_stdout
-
$stderr = orig_stderr
-
end
-
end
-
-
return captured_stdout.string, captured_stderr.string
-
end
-
-
##
-
# Captures $stdout and $stderr into strings, using Tempfile to
-
# ensure that subprocess IO is captured as well.
-
#
-
# out, err = capture_subprocess_io do
-
# system "echo Some info"
-
# system "echo You did a bad thing 1>&2"
-
# end
-
#
-
# assert_match %r%info%, out
-
# assert_match %r%bad%, err
-
#
-
# NOTE: This method is approximately 10x slower than #capture_io so
-
# only use it when you need to test the output of a subprocess.
-
-
1
def capture_subprocess_io
-
require 'tempfile'
-
-
captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err")
-
-
synchronize do
-
orig_stdout, orig_stderr = $stdout.dup, $stderr.dup
-
$stdout.reopen captured_stdout
-
$stderr.reopen captured_stderr
-
-
begin
-
yield
-
-
$stdout.rewind
-
$stderr.rewind
-
-
[captured_stdout.read, captured_stderr.read]
-
ensure
-
captured_stdout.unlink
-
captured_stderr.unlink
-
$stdout.reopen orig_stdout
-
$stderr.reopen orig_stderr
-
end
-
end
-
end
-
-
##
-
# Returns details for exception +e+
-
-
1
def exception_details e, msg
-
[
-
439
"#{msg}",
-
"Class: <#{e.class}>",
-
"Message: <#{e.message.inspect}>",
-
"---Backtrace---",
-
"#{MiniTest::filter_backtrace(e.backtrace).join("\n")}",
-
"---------------",
-
].join "\n"
-
end
-
-
##
-
# Fails with +msg+
-
-
1
def flunk msg = nil
-
msg ||= "Epic Fail!"
-
assert false, msg
-
end
-
-
##
-
# Returns a proc that will output +msg+ along with the default message.
-
-
1
def message msg = nil, ending = ".", &default
-
14914
proc {
-
50
custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty?
-
50
"#{custom_message}#{default.call}#{ending}"
-
}
-
end
-
-
##
-
# used for counting assertions
-
-
1
def pass msg = nil
-
assert true
-
end
-
-
##
-
# Fails if +test+ is a true value
-
-
1
def refute test, msg = nil
-
141
msg ||= "Failed refutation, no message given"
-
141
not assert(! test, msg)
-
end
-
-
##
-
# Fails if +obj+ is empty.
-
-
1
def refute_empty obj, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(obj)} to not be empty" }
-
assert_respond_to obj, :empty?
-
refute obj.empty?, msg
-
end
-
-
##
-
# Fails if <tt>exp == act</tt>.
-
#
-
# For floats use refute_in_delta.
-
-
1
def refute_equal exp, act, msg = nil
-
101
msg = message(msg) {
-
"Expected #{mu_pp(act)} to not be equal to #{mu_pp(exp)}"
-
}
-
101
refute exp == act, msg
-
end
-
-
##
-
# For comparing Floats. Fails if +exp+ is within +delta+ of +act+.
-
#
-
# refute_in_delta Math::PI, (22.0 / 7.0)
-
-
1
def refute_in_delta exp, act, delta = 0.001, msg = nil
-
n = (exp - act).abs
-
msg = message(msg) {
-
"Expected |#{exp} - #{act}| (#{n}) to not be < #{delta}"
-
}
-
refute delta > n, msg
-
end
-
-
##
-
# For comparing Floats. Fails if +exp+ and +act+ have a relative error
-
# less than +epsilon+.
-
-
1
def refute_in_epsilon a, b, epsilon = 0.001, msg = nil
-
refute_in_delta a, b, a * epsilon, msg
-
end
-
-
##
-
# Fails if +collection+ includes +obj+.
-
-
1
def refute_includes collection, obj, msg = nil
-
msg = message(msg) {
-
"Expected #{mu_pp(collection)} to not include #{mu_pp(obj)}"
-
}
-
assert_respond_to collection, :include?
-
refute collection.include?(obj), msg
-
end
-
-
##
-
# Fails if +obj+ is an instance of +cls+.
-
-
1
def refute_instance_of cls, obj, msg = nil
-
msg = message(msg) {
-
"Expected #{mu_pp(obj)} to not be an instance of #{cls}"
-
}
-
refute obj.instance_of?(cls), msg
-
end
-
-
##
-
# Fails if +obj+ is a kind of +cls+.
-
-
1
def refute_kind_of cls, obj, msg = nil # TODO: merge with instance_of
-
msg = message(msg) { "Expected #{mu_pp(obj)} to not be a kind of #{cls}" }
-
refute obj.kind_of?(cls), msg
-
end
-
-
##
-
# Fails if +matcher+ <tt>=~</tt> +obj+.
-
-
1
def refute_match matcher, obj, msg = nil
-
9
msg = message(msg) {"Expected #{mu_pp matcher} to not match #{mu_pp obj}"}
-
9
assert_respond_to matcher, :"=~"
-
9
matcher = Regexp.new Regexp.escape matcher if String === matcher
-
9
refute matcher =~ obj, msg
-
end
-
-
##
-
# Fails if +obj+ is nil.
-
-
1
def refute_nil obj, msg = nil
-
30
msg = message(msg) { "Expected #{mu_pp(obj)} to not be nil" }
-
30
refute obj.nil?, msg
-
end
-
-
##
-
# Fails if +o1+ is not +op+ +o2+. Eg:
-
#
-
# refute_operator 1, :>, 2 #=> pass
-
# refute_operator 1, :<, 2 #=> fail
-
-
1
def refute_operator o1, op, o2 = UNDEFINED, msg = nil
-
return refute_predicate o1, op, msg if UNDEFINED == o2
-
msg = message(msg) { "Expected #{mu_pp(o1)} to not be #{op} #{mu_pp(o2)}"}
-
refute o1.__send__(op, o2), msg
-
end
-
-
##
-
# For testing with predicates.
-
#
-
# refute_predicate str, :empty?
-
#
-
# This is really meant for specs and is front-ended by refute_operator:
-
#
-
# str.wont_be :empty?
-
-
1
def refute_predicate o1, op, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(o1)} to not be #{op}" }
-
refute o1.__send__(op), msg
-
end
-
-
##
-
# Fails if +obj+ responds to the message +meth+.
-
-
1
def refute_respond_to obj, meth, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(obj)} to not respond to #{meth}" }
-
-
refute obj.respond_to?(meth), msg
-
end
-
-
##
-
# Fails if +exp+ is the same (by object identity) as +act+.
-
-
1
def refute_same exp, act, msg = nil
-
msg = message(msg) {
-
data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id]
-
"Expected %s (oid=%d) to not be the same as %s (oid=%d)" % data
-
}
-
refute exp.equal?(act), msg
-
end
-
-
##
-
# Skips the current test. Gets listed at the end of the run but
-
# doesn't cause a failure exit code.
-
-
1
def skip msg = nil, bt = caller
-
msg ||= "Skipped, no message given"
-
raise MiniTest::Skip, msg, bt
-
end
-
-
##
-
# Takes a block and wraps it with the runner's shared mutex.
-
-
1
def synchronize
-
Minitest::Unit.runner.synchronize do
-
yield
-
end
-
end
-
end
-
-
1
class Unit # :nodoc:
-
1
VERSION = "4.2.0" # :nodoc:
-
-
1
attr_accessor :report, :failures, :errors, :skips # :nodoc:
-
1
attr_accessor :test_count, :assertion_count # :nodoc:
-
1
attr_accessor :start_time # :nodoc:
-
1
attr_accessor :help # :nodoc:
-
1
attr_accessor :verbose # :nodoc:
-
1
attr_writer :options # :nodoc:
-
-
##
-
# Lazy accessor for options.
-
-
1
def options
-
381
@options ||= {}
-
end
-
-
1
@@installed_at_exit ||= false
-
1
@@out = $stdout
-
1
@@after_tests = []
-
-
##
-
# A simple hook allowing you to run a block of code after _all_ of
-
# the tests are done. Eg:
-
#
-
# MiniTest::Unit.after_tests { p $debugging_info }
-
-
1
def self.after_tests &block
-
@@after_tests << block
-
end
-
-
##
-
# Registers MiniTest::Unit to run tests at process exit
-
-
1
def self.autorun
-
at_exit {
-
1
next if $! # don't run if there was an exception
-
-
# the order here is important. The at_exit handler must be
-
# installed before anyone else gets a chance to install their
-
# own, that way we can be assured that our exit will be last
-
# to run (at_exit stacks).
-
1
exit_code = nil
-
-
1
at_exit {
-
1
@@after_tests.reverse_each(&:call)
-
1
exit false if exit_code && exit_code != 0
-
}
-
-
1
exit_code = MiniTest::Unit.new.run ARGV
-
1
} unless @@installed_at_exit
-
1
@@installed_at_exit = true
-
end
-
-
##
-
# Returns the stream to use for output.
-
-
1
def self.output
-
3970
@@out
-
end
-
-
##
-
# Sets MiniTest::Unit to write output to +stream+. $stdout is the default
-
# output
-
-
1
def self.output= stream
-
@@out = stream
-
end
-
-
##
-
# Tells MiniTest::Unit to delegate to +runner+, an instance of a
-
# MiniTest::Unit subclass, when MiniTest::Unit#run is called.
-
-
1
def self.runner= runner
-
@@runner = runner
-
end
-
-
##
-
# Returns the MiniTest::Unit subclass instance that will be used
-
# to run the tests. A MiniTest::Unit instance is the default
-
# runner.
-
-
1
def self.runner
-
1
@@runner ||= self.new
-
end
-
-
##
-
# Return all plugins' run methods (methods that start with "run_").
-
-
1
def self.plugins
-
@@plugins ||= (["run_tests"] +
-
public_instance_methods(false).
-
2
grep(/^run_/).map { |s| s.to_s }).uniq
-
end
-
-
##
-
# Return the IO for output.
-
-
1
def output
-
3970
self.class.output
-
end
-
-
1
def puts *a # :nodoc:
-
17
output.puts(*a)
-
end
-
-
1
def print *a # :nodoc:
-
3948
output.print(*a)
-
end
-
-
##
-
# Runner for a given +type+ (eg, test vs bench).
-
-
1
def _run_anything type
-
1
suites = TestCase.send "#{type}_suites"
-
1
return if suites.empty?
-
-
1
start = Time.now
-
-
1
puts
-
1
puts "# Running #{type}s:"
-
1
puts
-
-
1
@test_count, @assertion_count = 0, 0
-
1
sync = output.respond_to? :"sync=" # stupid emacs
-
1
old_sync, output.sync = output.sync, true if sync
-
-
1
results = _run_suites suites, type
-
-
382
@test_count = results.inject(0) { |sum, (tc, _)| sum + tc }
-
382
@assertion_count = results.inject(0) { |sum, (_, ac)| sum + ac }
-
-
1
output.sync = old_sync if sync
-
-
1
t = Time.now - start
-
-
1
puts
-
1
puts
-
puts "Finished #{type}s in %.6fs, %.4f tests/s, %.4f assertions/s." %
-
1
[t, test_count / t, assertion_count / t]
-
-
1
report.each_with_index do |msg, i|
-
9
puts "\n%3d) %s" % [i + 1, msg]
-
end
-
-
1
puts
-
-
1
status
-
end
-
-
##
-
# Runs all the +suites+ for a given +type+. Runs suites declaring
-
# a test_order of +:parallel+ in parallel, and everything else
-
# serial.
-
-
1
def _run_suites suites, type
-
382
parallel, serial = suites.partition { |s| s.test_order == :parallel }
-
-
ParallelEach.new(parallel).map { |suite| _run_suite suite, type } +
-
382
serial.map { |suite| _run_suite suite, type }
-
end
-
-
##
-
# Run a single +suite+ for a given +type+.
-
-
1
def _run_suite suite, type
-
381
header = "#{type}_suite_header"
-
381
puts send(header, suite) if respond_to? header
-
-
381
filter = options[:filter] || '/./'
-
381
filter = Regexp.new $1 if filter =~ /\/(.*)\//
-
-
381
assertions = suite.send("#{type}_methods").grep(filter).map { |method|
-
3948
inst = suite.new method
-
3948
inst._assertions = 0
-
-
3948
print "#{suite}##{method} = " if @verbose
-
-
3948
start_time = Time.now if @verbose
-
3948
result = inst.run self
-
-
3948
print "%.2f s = " % (Time.now - start_time) if @verbose
-
3948
print result
-
3948
puts if @verbose
-
-
3948
inst._assertions
-
}
-
-
4329
return assertions.size, assertions.inject(0) { |sum, n| sum + n }
-
end
-
-
##
-
# Record the result of a single run. Makes it very easy to gather
-
# information. Eg:
-
#
-
# class StatisticsRecorder < MiniTest::Unit
-
# def record suite, method, assertions, time, error
-
# # ... record the results somewhere ...
-
# end
-
# end
-
#
-
# MiniTest::Unit.runner = StatisticsRecorder.new
-
-
1
def record suite, method, assertions, time, error
-
end
-
-
1
def location e # :nodoc:
-
7
last_before_assertion = ""
-
7
e.backtrace.reverse_each do |s|
-
112
break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
-
105
last_before_assertion = s
-
end
-
7
last_before_assertion.sub(/:in .*$/, '')
-
end
-
-
##
-
# Writes status for failed test +meth+ in +klass+ which finished with
-
# exception +e+
-
-
1
def puke klass, meth, e
-
9
e = case e
-
when MiniTest::Skip then
-
@skips += 1
-
return "S" unless @verbose
-
"Skipped:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n"
-
when MiniTest::Assertion then
-
7
@failures += 1
-
7
"Failure:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n"
-
else
-
2
@errors += 1
-
2
bt = MiniTest::filter_backtrace(e.backtrace).join "\n "
-
2
"Error:\n#{meth}(#{klass}):\n#{e.class}: #{e.message}\n #{bt}\n"
-
end
-
9
@report << e
-
9
e[0, 1]
-
end
-
-
1
def initialize # :nodoc:
-
2
@report = []
-
2
@errors = @failures = @skips = 0
-
2
@verbose = false
-
2
@mutex = Mutex.new
-
end
-
-
1
def synchronize # :nodoc:
-
@mutex.synchronize { yield }
-
end
-
-
1
def process_args args = [] # :nodoc:
-
1
options = {}
-
1
orig_args = args.dup
-
-
1
OptionParser.new do |opts|
-
1
opts.banner = 'minitest options:'
-
1
opts.version = MiniTest::Unit::VERSION
-
-
1
opts.on '-h', '--help', 'Display this help.' do
-
puts opts
-
exit
-
end
-
-
1
opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m|
-
options[:seed] = m.to_i
-
end
-
-
1
opts.on '-v', '--verbose', "Verbose. Show progress processing files." do
-
options[:verbose] = true
-
end
-
-
1
opts.on '-n', '--name PATTERN', "Filter test names on pattern (e.g. /foo/)" do |a|
-
options[:filter] = a
-
end
-
-
1
opts.parse! args
-
1
orig_args -= args
-
end
-
-
1
unless options[:seed] then
-
1
srand
-
1
options[:seed] = srand % 0xFFFF
-
1
orig_args << "--seed" << options[:seed].to_s
-
end
-
-
1
srand options[:seed]
-
-
1
self.verbose = options[:verbose]
-
3
@help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " "
-
-
1
options
-
end
-
-
##
-
# Begins the full test run. Delegates to +runner+'s #_run method.
-
-
1
def run args = []
-
1
self.class.runner._run(args)
-
end
-
-
##
-
# Top level driver, controls all output and filtering.
-
-
1
def _run args = []
-
1
self.options = process_args args
-
-
1
puts "Run options: #{help}"
-
-
1
self.class.plugins.each do |plugin|
-
1
send plugin
-
1
break unless report.empty?
-
end
-
-
1
return failures + errors if @test_count > 0 # or return nil...
-
rescue Interrupt
-
abort 'Interrupted'
-
end
-
-
##
-
# Runs test suites matching +filter+.
-
-
1
def run_tests
-
1
_run_anything :test
-
end
-
-
##
-
# Writes status to +io+
-
-
1
def status io = self.output
-
1
format = "%d tests, %d assertions, %d failures, %d errors, %d skips"
-
1
io.puts format % [test_count, assertion_count, failures, errors, skips]
-
end
-
-
##
-
# Provides a simple set of guards that you can use in your tests
-
# to skip execution if it is not applicable. These methods are
-
# mixed into TestCase as both instance and class methods so you
-
# can use them inside or outside of the test methods.
-
#
-
# def test_something_for_mri
-
# skip "bug 1234" if jruby?
-
# # ...
-
# end
-
#
-
# if windows? then
-
# # ... lots of test methods ...
-
# end
-
-
1
module Guard
-
-
##
-
# Is this running on jruby?
-
-
1
def jruby? platform = RUBY_PLATFORM
-
"java" == platform
-
end
-
-
##
-
# Is this running on mri?
-
-
1
def mri? platform = RUBY_DESCRIPTION
-
/^ruby/ =~ platform
-
end
-
-
##
-
# Is this running on rubinius?
-
-
1
def rubinius? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE
-
"rbx" == platform
-
end
-
-
##
-
# Is this running on windows?
-
-
1
def windows? platform = RUBY_PLATFORM
-
/mswin|mingw/ =~ platform
-
end
-
end
-
-
##
-
# Provides before/after hooks for setup and teardown. These are
-
# meant for library writers, NOT for regular test authors. See
-
# #before_setup for an example.
-
-
1
module LifecycleHooks
-
##
-
# Runs before every test, after setup. This hook is meant for
-
# libraries to extend minitest. It is not meant to be used by
-
# test developers.
-
#
-
# See #before_setup for an example.
-
-
1
def after_setup; end
-
-
##
-
# Runs before every test, before setup. This hook is meant for
-
# libraries to extend minitest. It is not meant to be used by
-
# test developers.
-
#
-
# As a simplistic example:
-
#
-
# module MyMinitestPlugin
-
# def before_setup
-
# super
-
# # ... stuff to do before setup is run
-
# end
-
#
-
# def after_setup
-
# # ... stuff to do after setup is run
-
# super
-
# end
-
#
-
# def before_teardown
-
# super
-
# # ... stuff to do before teardown is run
-
# end
-
#
-
# def after_teardown
-
# # ... stuff to do after teardown is run
-
# super
-
# end
-
# end
-
#
-
# class MiniTest::Unit::TestCase
-
# include MyMinitestPlugin
-
# end
-
-
1
def before_setup; end
-
-
##
-
# Runs after every test, before teardown. This hook is meant for
-
# libraries to extend minitest. It is not meant to be used by
-
# test developers.
-
#
-
# See #before_setup for an example.
-
-
1
def before_teardown; end
-
-
##
-
# Runs after every test, after teardown. This hook is meant for
-
# libraries to extend minitest. It is not meant to be used by
-
# test developers.
-
#
-
# See #before_setup for an example.
-
-
1
def after_teardown; end
-
end
-
-
1
module Deprecated # :nodoc:
-
-
##
-
# This entire module is deprecated and slated for removal on 2013-01-01.
-
-
1
module Hooks
-
1
def run_setup_hooks # :nodoc:
-
_run_hooks self.class.setup_hooks
-
end
-
-
1
def _run_hooks hooks # :nodoc:
-
hooks.each do |hook|
-
if hook.respond_to?(:arity) && hook.arity == 1
-
hook.call(self)
-
else
-
hook.call
-
end
-
end
-
end
-
-
1
def run_teardown_hooks # :nodoc:
-
_run_hooks self.class.teardown_hooks.reverse
-
end
-
end
-
-
##
-
# This entire module is deprecated and slated for removal on 2013-01-01.
-
-
1
module HooksCM
-
##
-
# Adds a block of code that will be executed before every
-
# TestCase is run.
-
#
-
# NOTE: This method is deprecated, use before/after_setup. It
-
# will be removed on 2013-01-01.
-
-
1
def add_setup_hook arg=nil, &block
-
warn "NOTE: MiniTest::Unit::TestCase.add_setup_hook is deprecated, use before/after_setup via a module (and call super!). It will be removed on 2013-01-01. Called from #{caller.first}"
-
hook = arg || block
-
@setup_hooks << hook
-
end
-
-
1
def setup_hooks # :nodoc:
-
if superclass.respond_to? :setup_hooks then
-
superclass.setup_hooks
-
else
-
[]
-
end + @setup_hooks
-
end
-
-
##
-
# Adds a block of code that will be executed after every
-
# TestCase is run.
-
#
-
# NOTE: This method is deprecated, use before/after_teardown. It
-
# will be removed on 2013-01-01.
-
-
1
def add_teardown_hook arg=nil, &block
-
warn "NOTE: MiniTest::Unit::TestCase#add_teardown_hook is deprecated, use before/after_teardown. It will be removed on 2013-01-01. Called from #{caller.first}"
-
hook = arg || block
-
@teardown_hooks << hook
-
end
-
-
1
def teardown_hooks # :nodoc:
-
if superclass.respond_to? :teardown_hooks then
-
superclass.teardown_hooks
-
else
-
[]
-
end + @teardown_hooks
-
end
-
end
-
end
-
-
##
-
# Subclass TestCase to create your own tests. Typically you'll want a
-
# TestCase subclass per implementation class.
-
#
-
# See MiniTest::Assertions
-
-
1
class TestCase
-
1
include LifecycleHooks
-
1
include Deprecated::Hooks
-
1
extend Deprecated::HooksCM # UGH... I can't wait 'til 2013!
-
1
include Guard
-
1
extend Guard
-
-
1
attr_reader :__name__ # :nodoc:
-
-
1
PASSTHROUGH_EXCEPTIONS = [NoMemoryError, SignalException,
-
Interrupt, SystemExit] # :nodoc:
-
-
1
SUPPORTS_INFO_SIGNAL = Signal.list['INFO'] # :nodoc:
-
-
##
-
# Runs the tests reporting the status to +runner+
-
-
1
def run runner
-
trap "INFO" do
-
runner.report.each_with_index do |msg, i|
-
warn "\n%3d) %s" % [i + 1, msg]
-
end
-
warn ''
-
time = runner.start_time ? Time.now - runner.start_time : 0
-
warn "Current Test: %s#%s %.2fs" % [self.class, self.__name__, time]
-
runner.status $stderr
-
3948
end if SUPPORTS_INFO_SIGNAL
-
-
3948
start_time = Time.now
-
-
3948
result = ""
-
3948
begin
-
3948
@passed = nil
-
3948
self.before_setup
-
3948
self.setup
-
3948
self.after_setup
-
3948
self.run_test self.__name__
-
3939
result = "." unless io?
-
3939
time = Time.now - start_time
-
3939
runner.record self.class, self.__name__, self._assertions, time, nil
-
3939
@passed = true
-
rescue *PASSTHROUGH_EXCEPTIONS
-
raise
-
rescue Exception => e
-
9
@passed = false
-
9
time = Time.now - start_time
-
9
runner.record self.class, self.__name__, self._assertions, time, e
-
9
result = runner.puke self.class, self.__name__, e
-
ensure
-
3948
%w{ before_teardown teardown after_teardown }.each do |hook|
-
11844
begin
-
11844
self.send hook
-
rescue *PASSTHROUGH_EXCEPTIONS
-
raise
-
rescue Exception => e
-
@passed = false
-
result = runner.puke self.class, self.__name__, e
-
end
-
end
-
3948
trap 'INFO', 'DEFAULT' if SUPPORTS_INFO_SIGNAL
-
end
-
3948
result
-
end
-
-
1
alias :run_test :__send__
-
-
1
def initialize name # :nodoc:
-
3950
@__name__ = name
-
3950
@__io__ = nil
-
3950
@passed = nil
-
3950
@@current = self
-
end
-
-
1
def self.current # :nodoc:
-
@@current
-
end
-
-
##
-
# Return the output IO object
-
-
1
def io
-
@__io__ = true
-
MiniTest::Unit.output
-
end
-
-
##
-
# Have we hooked up the IO yet?
-
-
1
def io?
-
3939
@__io__
-
end
-
-
1
def self.reset # :nodoc:
-
1
@@test_suites = {}
-
end
-
-
1
reset
-
-
##
-
# Call this at the top of your tests when you absolutely
-
# positively need to have ordered tests. In doing so, you're
-
# admitting that you suck and your tests are weak.
-
-
1
def self.i_suck_and_my_tests_are_order_dependent!
-
class << self
-
undef_method :test_order if method_defined? :test_order
-
define_method :test_order do :alpha end
-
end
-
end
-
-
##
-
# Make diffs for this TestCase use #pretty_inspect so that diff
-
# in assert_equal can be more details. NOTE: this is much slower
-
# than the regular inspect but much more usable for complex
-
# objects.
-
-
1
def self.make_my_diffs_pretty!
-
require 'pp'
-
-
define_method :mu_pp do |o|
-
o.pretty_inspect
-
end
-
end
-
-
##
-
# Call this at the top of your tests when you want to run your
-
# tests in parallel. In doing so, you're admitting that you rule
-
# and your tests are awesome.
-
-
1
def self.parallelize_me!
-
class << self
-
undef_method :test_order if method_defined? :test_order
-
define_method :test_order do :parallel end
-
end
-
end
-
-
1
def self.inherited klass # :nodoc:
-
381
@@test_suites[klass] = true
-
381
klass.reset_setup_teardown_hooks
-
381
super
-
end
-
-
1
def self.test_order # :nodoc:
-
2
:random
-
end
-
-
1
def self.test_suites # :nodoc:
-
382
@@test_suites.keys.sort_by { |ts| ts.name.to_s }
-
end
-
-
1
def self.test_methods # :nodoc:
-
4329
methods = public_instance_methods(true).grep(/^test/).map { |m| m.to_s }
-
-
381
case self.test_order
-
when :parallel
-
max = methods.size
-
ParallelEach.new methods.sort.sort_by { rand max }
-
when :random then
-
1
max = methods.size
-
1
methods.sort.sort_by { rand max }
-
when :alpha, :sorted then
-
380
methods.sort
-
else
-
raise "Unknown test_order: #{self.test_order.inspect}"
-
end
-
end
-
-
##
-
# Returns true if the test passed.
-
-
1
def passed?
-
@passed
-
end
-
-
##
-
# Runs before every test. Use this to set up before each test
-
# run.
-
-
1
def setup; end
-
-
##
-
# Runs after every test. Use this to clean up after each test
-
# run.
-
-
1
def teardown; end
-
-
1
def self.reset_setup_teardown_hooks # :nodoc:
-
# also deprecated... believe it.
-
382
@setup_hooks = []
-
382
@teardown_hooks = []
-
end
-
-
1
reset_setup_teardown_hooks
-
-
1
include MiniTest::Assertions
-
end # class TestCase
-
end # class Unit
-
end # module MiniTest
-
-
1
Minitest = MiniTest # :nodoc: because ugh... I typo this all the time
-
-
1
if $DEBUG then
-
module Test # :nodoc:
-
module Unit # :nodoc:
-
class TestCase # :nodoc:
-
def self.inherited x # :nodoc:
-
# this helps me ferret out porting issues
-
raise "Using minitest and test/unit in the same process: #{x}"
-
end
-
end
-
end
-
end
-
end
-
1
module MultiJson
-
1
module Adapters
-
1
module JsonCommon
-
-
1
def load(string, options={})
-
6
string = string.read if string.respond_to?(:read)
-
6
::JSON.parse(string, :symbolize_names => options[:symbolize_keys])
-
end
-
-
1
def dump(object, options={})
-
object.to_json(process_options(options))
-
end
-
-
1
protected
-
-
1
def process_options(options={})
-
return options if options.empty?
-
opts = {}
-
opts.merge!(JSON::PRETTY_STATE_PROTOTYPE.to_h) if options.delete(:pretty)
-
opts.merge!(options)
-
end
-
-
end
-
end
-
end
-
1
require 'json' unless defined?(::JSON)
-
1
require 'multi_json/adapters/json_common'
-
-
1
module MultiJson
-
1
module Adapters
-
# Use the JSON gem to dump/load.
-
1
class JsonGem
-
1
ParseError = ::JSON::ParserError
-
1
extend JsonCommon
-
end
-
end
-
end
-
# Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
-
#
-
# Rack is freely distributable under the terms of an MIT-style license.
-
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-
# The Rack main module, serving as a namespace for all core Rack
-
# modules and classes.
-
#
-
# All modules meant for use in your application are <tt>autoload</tt>ed here,
-
# so it should be enough just to <tt>require rack.rb</tt> in your code.
-
-
1
module Rack
-
# The Rack protocol version number implemented.
-
1
VERSION = [1,1]
-
-
# Return the Rack protocol version as a dotted string.
-
1
def self.version
-
VERSION.join(".")
-
end
-
-
# Return the Rack release as a dotted string.
-
1
def self.release
-
"1.4"
-
end
-
-
1
autoload :Builder, "rack/builder"
-
1
autoload :BodyProxy, "rack/body_proxy"
-
1
autoload :Cascade, "rack/cascade"
-
1
autoload :Chunked, "rack/chunked"
-
1
autoload :CommonLogger, "rack/commonlogger"
-
1
autoload :ConditionalGet, "rack/conditionalget"
-
1
autoload :Config, "rack/config"
-
1
autoload :ContentLength, "rack/content_length"
-
1
autoload :ContentType, "rack/content_type"
-
1
autoload :ETag, "rack/etag"
-
1
autoload :File, "rack/file"
-
1
autoload :Deflater, "rack/deflater"
-
1
autoload :Directory, "rack/directory"
-
1
autoload :ForwardRequest, "rack/recursive"
-
1
autoload :Handler, "rack/handler"
-
1
autoload :Head, "rack/head"
-
1
autoload :Lint, "rack/lint"
-
1
autoload :Lock, "rack/lock"
-
1
autoload :Logger, "rack/logger"
-
1
autoload :MethodOverride, "rack/methodoverride"
-
1
autoload :Mime, "rack/mime"
-
1
autoload :NullLogger, "rack/nulllogger"
-
1
autoload :Recursive, "rack/recursive"
-
1
autoload :Reloader, "rack/reloader"
-
1
autoload :Runtime, "rack/runtime"
-
1
autoload :Sendfile, "rack/sendfile"
-
1
autoload :Server, "rack/server"
-
1
autoload :ShowExceptions, "rack/showexceptions"
-
1
autoload :ShowStatus, "rack/showstatus"
-
1
autoload :Static, "rack/static"
-
1
autoload :URLMap, "rack/urlmap"
-
1
autoload :Utils, "rack/utils"
-
1
autoload :Multipart, "rack/multipart"
-
-
1
autoload :MockRequest, "rack/mock"
-
1
autoload :MockResponse, "rack/mock"
-
-
1
autoload :Request, "rack/request"
-
1
autoload :Response, "rack/response"
-
-
1
module Auth
-
1
autoload :Basic, "rack/auth/basic"
-
1
autoload :AbstractRequest, "rack/auth/abstract/request"
-
1
autoload :AbstractHandler, "rack/auth/abstract/handler"
-
1
module Digest
-
1
autoload :MD5, "rack/auth/digest/md5"
-
1
autoload :Nonce, "rack/auth/digest/nonce"
-
1
autoload :Params, "rack/auth/digest/params"
-
1
autoload :Request, "rack/auth/digest/request"
-
end
-
end
-
-
1
module Session
-
1
autoload :Cookie, "rack/session/cookie"
-
1
autoload :Pool, "rack/session/pool"
-
1
autoload :Memcache, "rack/session/memcache"
-
end
-
end
-
1
module Rack
-
1
class BodyProxy
-
1
def initialize(body, &block)
-
14
@body, @block, @closed = body, block, false
-
end
-
-
1
def respond_to?(*args)
-
7
super or @body.respond_to?(*args)
-
end
-
-
1
def close
-
10
return if @closed
-
10
@closed = true
-
10
begin
-
10
@body.close if @body.respond_to? :close
-
ensure
-
10
@block.call
-
end
-
end
-
-
1
def closed?
-
@closed
-
end
-
-
1
def method_missing(*args, &block)
-
5
@body.__send__(*args, &block)
-
end
-
end
-
end
-
1
module Rack
-
# Rack::Builder implements a small DSL to iteratively construct Rack
-
# applications.
-
#
-
# Example:
-
#
-
# require 'rack/lobster'
-
# app = Rack::Builder.new do
-
# use Rack::CommonLogger
-
# use Rack::ShowExceptions
-
# map "/lobster" do
-
# use Rack::Lint
-
# run Rack::Lobster.new
-
# end
-
# end
-
#
-
# run app
-
#
-
# Or
-
#
-
# app = Rack::Builder.app do
-
# use Rack::CommonLogger
-
# run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
-
# end
-
#
-
# run app
-
#
-
# +use+ adds a middleware to the stack, +run+ dispatches to an application.
-
# You can use +map+ to construct a Rack::URLMap in a convenient way.
-
-
1
class Builder
-
1
def self.parse_file(config, opts = Server::Options.new)
-
options = {}
-
if config =~ /\.ru$/
-
cfgfile = ::File.read(config)
-
if cfgfile[/^#\\(.*)/] && opts
-
options = opts.parse! $1.split(/\s+/)
-
end
-
cfgfile.sub!(/^__END__\n.*\Z/m, '')
-
app = eval "Rack::Builder.new {\n" + cfgfile + "\n}.to_app",
-
TOPLEVEL_BINDING, config
-
else
-
require config
-
app = Object.const_get(::File.basename(config, '.rb').capitalize)
-
end
-
return app, options
-
end
-
-
1
def initialize(default_app = nil,&block)
-
2
@use, @map, @run = [], nil, default_app
-
2
instance_eval(&block) if block_given?
-
end
-
-
1
def self.app(default_app = nil, &block)
-
self.new(default_app, &block).to_app
-
end
-
-
# Specifies a middleware to use in a stack.
-
#
-
# class Middleware
-
# def initialize(app)
-
# @app = app
-
# end
-
#
-
# def call(env)
-
# env["rack.some_header"] = "setting an example"
-
# @app.call(env)
-
# end
-
# end
-
#
-
# use Middleware
-
# run lambda { |env| [200, { "Content-Type => "text/plain" }, ["OK"]] }
-
#
-
# All requests through to this application will first be processed by the middleware class.
-
# The +call+ method in this example sets an additional environment key which then can be
-
# referenced in the application if required.
-
1
def use(middleware, *args, &block)
-
2
if @map
-
mapping, @map = @map, nil
-
@use << proc { |app| generate_map app, mapping }
-
end
-
4
@use << proc { |app| middleware.new(app, *args, &block) }
-
end
-
-
# Takes an argument that is an object that responds to #call and returns a Rack response.
-
# The simplest form of this is a lambda object:
-
#
-
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
-
#
-
# However this could also be a class:
-
#
-
# class Heartbeat
-
# def self.call(env)
-
# [200, { "Content-Type" => "text/plain" }, ["OK"]]
-
# end
-
# end
-
#
-
# run Heartbeat
-
1
def run(app)
-
2
@run = app
-
end
-
-
# Creates a route within the application.
-
#
-
# Rack::Builder.app do
-
# map '/' do
-
# run Heartbeat
-
# end
-
# end
-
#
-
# The +use+ method can also be used here to specify middleware to run under a specific path:
-
#
-
# Rack::Builder.app do
-
# map '/' do
-
# use Middleware
-
# run Heartbeat
-
# end
-
# end
-
#
-
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
-
#
-
1
def map(path, &block)
-
@map ||= {}
-
@map[path] = block
-
end
-
-
1
def to_app
-
2
app = @map ? generate_map(@run, @map) : @run
-
2
fail "missing run or map statement" unless app
-
4
@use.reverse.inject(app) { |a,e| e[a] }
-
end
-
-
1
def call(env)
-
to_app.call(env)
-
end
-
-
1
private
-
-
1
def generate_map(default_app, mapping)
-
mapped = default_app ? {'/' => default_app} : {}
-
mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b) }
-
URLMap.new(mapped)
-
end
-
end
-
end
-
1
require 'rack/utils'
-
-
1
module Rack
-
-
# Middleware that applies chunked transfer encoding to response bodies
-
# when the response does not include a Content-Length header.
-
1
class Chunked
-
1
include Rack::Utils
-
-
# A body wrapper that emits chunked responses
-
1
class Body
-
1
TERM = "\r\n"
-
1
TAIL = "0#{TERM}#{TERM}"
-
-
1
include Rack::Utils
-
-
1
def initialize(body)
-
8
@body = body
-
end
-
-
1
def each
-
8
term = TERM
-
8
@body.each do |chunk|
-
13
size = bytesize(chunk)
-
13
next if size == 0
-
-
13
chunk = chunk.dup.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding)
-
13
yield [size.to_s(16), term, chunk, term].join
-
end
-
8
yield TAIL
-
end
-
-
1
def close
-
@body.close if @body.respond_to?(:close)
-
end
-
end
-
-
1
def initialize(app)
-
@app = app
-
end
-
-
1
def call(env)
-
status, headers, body = @app.call(env)
-
headers = HeaderHash.new(headers)
-
-
if env['HTTP_VERSION'] == 'HTTP/1.0' ||
-
STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
-
headers['Content-Length'] ||
-
headers['Transfer-Encoding']
-
[status, headers, body]
-
else
-
headers.delete('Content-Length')
-
headers['Transfer-Encoding'] = 'chunked'
-
[status, headers, Body.new(body)]
-
end
-
end
-
end
-
end
-
1
require 'time'
-
1
require 'rack/utils'
-
1
require 'rack/mime'
-
-
1
module Rack
-
# Rack::File serves files below the +root+ directory given, according to the
-
# path info of the Rack request.
-
# e.g. when Rack::File.new("/etc") is used, you can access 'passwd' file
-
# as http://localhost:9292/passwd
-
#
-
# Handlers can detect if bodies are a Rack::File, and use mechanisms
-
# like sendfile on the +path+.
-
-
1
class File
-
1
SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
-
1
ALLOWED_VERBS = %w[GET HEAD]
-
-
1
attr_accessor :root
-
1
attr_accessor :path
-
1
attr_accessor :cache_control
-
-
1
alias :to_path :path
-
-
1
def initialize(root, cache_control = nil)
-
1
@root = root
-
1
@cache_control = cache_control
-
end
-
-
1
def call(env)
-
32
dup._call(env)
-
end
-
-
1
F = ::File
-
-
1
def _call(env)
-
32
unless ALLOWED_VERBS.include? env["REQUEST_METHOD"]
-
return fail(405, "Method Not Allowed")
-
end
-
-
32
@path_info = Utils.unescape(env["PATH_INFO"])
-
32
parts = @path_info.split SEPS
-
-
32
parts.inject(0) do |depth, part|
-
91
case part
-
when '', '.'
-
32
depth
-
when '..'
-
return fail(404, "Not Found") if depth - 1 < 0
-
depth - 1
-
else
-
59
depth + 1
-
end
-
end
-
-
32
@path = F.join(@root, *parts)
-
-
32
available = begin
-
32
F.file?(@path) && F.readable?(@path)
-
rescue SystemCallError
-
false
-
end
-
-
32
if available
-
32
serving(env)
-
else
-
fail(404, "File not found: #{@path_info}")
-
end
-
end
-
-
1
def serving(env)
-
32
last_modified = F.mtime(@path).httpdate
-
32
return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
-
32
response = [
-
200,
-
{
-
"Last-Modified" => last_modified,
-
"Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain')
-
},
-
env["REQUEST_METHOD"] == "HEAD" ? [] : self
-
]
-
32
response[1].merge! 'Cache-Control' => @cache_control if @cache_control
-
-
# NOTE:
-
# We check via File::size? whether this file provides size info
-
# via stat (e.g. /proc files often don't), otherwise we have to
-
# figure it out by reading the whole file into memory.
-
32
size = F.size?(@path) || Utils.bytesize(F.read(@path))
-
-
32
ranges = Rack::Utils.byte_ranges(env, size)
-
32
if ranges.nil? || ranges.length > 1
-
# No ranges, or multiple ranges (which we don't support):
-
# TODO: Support multiple byte-ranges
-
32
response[0] = 200
-
32
@range = 0..size-1
-
elsif ranges.empty?
-
# Unsatisfiable. Return error, and file size:
-
response = fail(416, "Byte range unsatisfiable")
-
response[1]["Content-Range"] = "bytes */#{size}"
-
return response
-
else
-
# Partial content:
-
@range = ranges[0]
-
response[0] = 206
-
response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
-
size = @range.end - @range.begin + 1
-
end
-
-
32
response[1]["Content-Length"] = size.to_s
-
32
response
-
end
-
-
1
def each
-
32
F.open(@path, "rb") do |file|
-
32
file.seek(@range.begin)
-
32
remaining_len = @range.end-@range.begin+1
-
32
while remaining_len > 0
-
32
part = file.read([8192, remaining_len].min)
-
32
break unless part
-
32
remaining_len -= part.length
-
-
32
yield part
-
end
-
end
-
end
-
-
1
private
-
-
1
def fail(status, body)
-
body += "\n"
-
[
-
status,
-
{
-
"Content-Type" => "text/plain",
-
"Content-Length" => body.size.to_s,
-
"X-Cascade" => "pass"
-
},
-
[body]
-
]
-
end
-
-
end
-
end
-
1
module Rack
-
-
1
class Head
-
1
def initialize(app)
-
195
@app = app
-
end
-
-
1
def call(env)
-
287
status, headers, body = @app.call(env)
-
-
273
if env["REQUEST_METHOD"] == "HEAD"
-
3
[status, headers, []]
-
else
-
270
[status, headers, body]
-
end
-
end
-
end
-
-
end
-
1
require 'rack/utils'
-
-
1
module Rack
-
# Rack::Lint validates your application and the requests and
-
# responses according to the Rack spec.
-
-
1
class Lint
-
1
def initialize(app)
-
2
@app = app
-
2
@content_length = nil
-
end
-
-
# :stopdoc:
-
-
1
class LintError < RuntimeError; end
-
1
module Assertion
-
1
def assert(message, &block)
-
128
unless block.call
-
raise LintError, message
-
end
-
end
-
end
-
1
include Assertion
-
-
## This specification aims to formalize the Rack protocol. You
-
## can (and should) use Rack::Lint to enforce it.
-
##
-
## When you develop middleware, be sure to add a Lint before and
-
## after to catch all mistakes.
-
-
## = Rack applications
-
-
## A Rack application is a Ruby object (not a class) that
-
## responds to +call+.
-
1
def call(env=nil)
-
2
dup._call(env)
-
end
-
-
1
def _call(env)
-
## It takes exactly one argument, the *environment*
-
4
assert("No env given") { env }
-
2
check_env env
-
-
2
env['rack.input'] = InputWrapper.new(env['rack.input'])
-
2
env['rack.errors'] = ErrorWrapper.new(env['rack.errors'])
-
-
## and returns an Array of exactly three values:
-
2
status, headers, @body = @app.call(env)
-
## The *status*,
-
2
check_status status
-
## the *headers*,
-
2
check_headers headers
-
## and the *body*.
-
2
check_content_type status, headers
-
2
check_content_length status, headers
-
2
@head_request = env["REQUEST_METHOD"] == "HEAD"
-
2
[status, headers, self]
-
end
-
-
## == The Environment
-
1
def check_env(env)
-
## The environment must be an instance of Hash that includes
-
## CGI-like headers. The application is free to modify the
-
## environment.
-
2
assert("env #{env.inspect} is not a Hash, but #{env.class}") {
-
2
env.kind_of? Hash
-
}
-
-
##
-
## The environment is required to include these variables
-
## (adopted from PEP333), except when they'd be empty, but see
-
## below.
-
-
## <tt>REQUEST_METHOD</tt>:: The HTTP request method, such as
-
## "GET" or "POST". This cannot ever
-
## be an empty string, and so is
-
## always required.
-
-
## <tt>SCRIPT_NAME</tt>:: The initial portion of the request
-
## URL's "path" that corresponds to the
-
## application object, so that the
-
## application knows its virtual
-
## "location". This may be an empty
-
## string, if the application corresponds
-
## to the "root" of the server.
-
-
## <tt>PATH_INFO</tt>:: The remainder of the request URL's
-
## "path", designating the virtual
-
## "location" of the request's target
-
## within the application. This may be an
-
## empty string, if the request URL targets
-
## the application root and does not have a
-
## trailing slash. This value may be
-
## percent-encoded when I originating from
-
## a URL.
-
-
## <tt>QUERY_STRING</tt>:: The portion of the request URL that
-
## follows the <tt>?</tt>, if any. May be
-
## empty, but is always required!
-
-
## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>:: When combined with <tt>SCRIPT_NAME</tt> and <tt>PATH_INFO</tt>, these variables can be used to complete the URL. Note, however, that <tt>HTTP_HOST</tt>, if present, should be used in preference to <tt>SERVER_NAME</tt> for reconstructing the request URL. <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt> can never be empty strings, and so are always required.
-
-
## <tt>HTTP_</tt> Variables:: Variables corresponding to the
-
## client-supplied HTTP request
-
## headers (i.e., variables whose
-
## names begin with <tt>HTTP_</tt>). The
-
## presence or absence of these
-
## variables should correspond with
-
## the presence or absence of the
-
## appropriate HTTP header in the
-
## request.
-
-
## In addition to this, the Rack environment must include these
-
## Rack-specific variables:
-
-
## <tt>rack.version</tt>:: The Array [1,1], representing this version of Rack.
-
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
-
## <tt>rack.input</tt>:: See below, the input stream.
-
## <tt>rack.errors</tt>:: See below, the error stream.
-
## <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
-
## <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
-
## <tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
-
##
-
-
## Additional environment specifications have approved to
-
## standardized middleware APIs. None of these are required to
-
## be implemented by the server.
-
-
## <tt>rack.session</tt>:: A hash like interface for storing request session data.
-
## The store must implement:
-
2
if session = env['rack.session']
-
## store(key, value) (aliased as []=);
-
assert("session #{session.inspect} must respond to store and []=") {
-
session.respond_to?(:store) && session.respond_to?(:[]=)
-
}
-
-
## fetch(key, default = nil) (aliased as []);
-
assert("session #{session.inspect} must respond to fetch and []") {
-
session.respond_to?(:fetch) && session.respond_to?(:[])
-
}
-
-
## delete(key);
-
assert("session #{session.inspect} must respond to delete") {
-
session.respond_to?(:delete)
-
}
-
-
## clear;
-
assert("session #{session.inspect} must respond to clear") {
-
session.respond_to?(:clear)
-
}
-
end
-
-
## <tt>rack.logger</tt>:: A common object interface for logging messages.
-
## The object must implement:
-
2
if logger = env['rack.logger']
-
## info(message, &block)
-
assert("logger #{logger.inspect} must respond to info") {
-
logger.respond_to?(:info)
-
}
-
-
## debug(message, &block)
-
assert("logger #{logger.inspect} must respond to debug") {
-
logger.respond_to?(:debug)
-
}
-
-
## warn(message, &block)
-
assert("logger #{logger.inspect} must respond to warn") {
-
logger.respond_to?(:warn)
-
}
-
-
## error(message, &block)
-
assert("logger #{logger.inspect} must respond to error") {
-
logger.respond_to?(:error)
-
}
-
-
## fatal(message, &block)
-
assert("logger #{logger.inspect} must respond to fatal") {
-
logger.respond_to?(:fatal)
-
}
-
end
-
-
## The server or the application can store their own data in the
-
## environment, too. The keys must contain at least one dot,
-
## and should be prefixed uniquely. The prefix <tt>rack.</tt>
-
## is reserved for use with the Rack core distribution and other
-
## accepted specifications and must not be used otherwise.
-
##
-
-
2
%w[REQUEST_METHOD SERVER_NAME SERVER_PORT
-
QUERY_STRING
-
rack.version rack.input rack.errors
-
rack.multithread rack.multiprocess rack.run_once].each { |header|
-
40
assert("env missing required key #{header}") { env.include? header }
-
}
-
-
## The environment must not contain the keys
-
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
-
## (use the versions without <tt>HTTP_</tt>).
-
2
%w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
-
4
assert("env contains #{header}, must use #{header[5,-1]}") {
-
4
not env.include? header
-
}
-
}
-
-
## The CGI keys (named without a period) must have String values.
-
2
env.each { |key, value|
-
32
next if key.include? "." # Skip extensions
-
18
assert("env variable #{key} has non-string value #{value.inspect}") {
-
18
value.kind_of? String
-
}
-
}
-
-
##
-
## There are the following restrictions:
-
-
## * <tt>rack.version</tt> must be an array of Integers.
-
2
assert("rack.version must be an Array, was #{env["rack.version"].class}") {
-
2
env["rack.version"].kind_of? Array
-
}
-
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
-
2
assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") {
-
2
%w[http https].include? env["rack.url_scheme"]
-
}
-
-
## * There must be a valid input stream in <tt>rack.input</tt>.
-
2
check_input env["rack.input"]
-
## * There must be a valid error stream in <tt>rack.errors</tt>.
-
2
check_error env["rack.errors"]
-
-
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
-
2
assert("REQUEST_METHOD unknown: #{env["REQUEST_METHOD"]}") {
-
2
env["REQUEST_METHOD"] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
-
}
-
-
## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
-
2
assert("SCRIPT_NAME must start with /") {
-
!env.include?("SCRIPT_NAME") ||
-
2
env["SCRIPT_NAME"] == "" ||
-
env["SCRIPT_NAME"] =~ /\A\//
-
}
-
## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
-
2
assert("PATH_INFO must start with /") {
-
!env.include?("PATH_INFO") ||
-
2
env["PATH_INFO"] == "" ||
-
env["PATH_INFO"] =~ /\A\//
-
}
-
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
-
2
assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
-
2
!env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
-
}
-
-
## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
-
## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
-
## <tt>SCRIPT_NAME</tt> is empty.
-
2
assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
-
2
env["SCRIPT_NAME"] || env["PATH_INFO"]
-
}
-
## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
-
2
assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
-
2
env["SCRIPT_NAME"] != "/"
-
}
-
end
-
-
## === The Input Stream
-
##
-
## The input stream is an IO-like object which contains the raw HTTP
-
## POST data.
-
1
def check_input(input)
-
## When applicable, its external encoding must be "ASCII-8BIT" and it
-
## must be opened in binary mode, for Ruby 1.9 compatibility.
-
assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
-
2
input.external_encoding.name == "ASCII-8BIT"
-
2
} if input.respond_to?(:external_encoding)
-
assert("rack.input #{input} is not opened in binary mode") {
-
input.binmode?
-
2
} if input.respond_to?(:binmode?)
-
-
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
-
2
[:gets, :each, :read, :rewind].each { |method|
-
8
assert("rack.input #{input} does not respond to ##{method}") {
-
8
input.respond_to? method
-
}
-
}
-
end
-
-
1
class InputWrapper
-
1
include Assertion
-
-
1
def initialize(input)
-
2
@input = input
-
end
-
-
## * +gets+ must be called without arguments and return a string,
-
## or +nil+ on EOF.
-
1
def gets(*args)
-
assert("rack.input#gets called with arguments") { args.size == 0 }
-
v = @input.gets
-
assert("rack.input#gets didn't return a String") {
-
v.nil? or v.kind_of? String
-
}
-
v
-
end
-
-
## * +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
-
## If given, +length+ must be a non-negative Integer (>= 0) or +nil+, and +buffer+ must
-
## be a String and may not be nil. If +length+ is given and not nil, then this method
-
## reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
-
## then this method reads all data until EOF.
-
## When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
-
## if +length+ is not given or is nil.
-
## If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
-
## newly created String object.
-
1
def read(*args)
-
2
assert("rack.input#read called with too many arguments") {
-
2
args.size <= 2
-
}
-
2
if args.size >= 1
-
2
assert("rack.input#read called with non-integer and non-nil length") {
-
2
args.first.kind_of?(Integer) || args.first.nil?
-
}
-
2
assert("rack.input#read called with a negative length") {
-
2
args.first.nil? || args.first >= 0
-
}
-
end
-
2
if args.size >= 2
-
assert("rack.input#read called with non-String buffer") {
-
args[1].kind_of?(String)
-
}
-
end
-
-
2
v = @input.read(*args)
-
-
2
assert("rack.input#read didn't return nil or a String") {
-
2
v.nil? or v.kind_of? String
-
}
-
2
if args[0].nil?
-
assert("rack.input#read(nil) returned nil on EOF") {
-
!v.nil?
-
}
-
end
-
-
2
v
-
end
-
-
## * +each+ must be called without arguments and only yield Strings.
-
1
def each(*args)
-
assert("rack.input#each called with arguments") { args.size == 0 }
-
@input.each { |line|
-
assert("rack.input#each didn't yield a String") {
-
line.kind_of? String
-
}
-
yield line
-
}
-
end
-
-
## * +rewind+ must be called without arguments. It rewinds the input
-
## stream back to the beginning. It must not raise Errno::ESPIPE:
-
## that is, it may not be a pipe or a socket. Therefore, handler
-
## developers must buffer the input data into some rewindable object
-
## if the underlying input stream is not rewindable.
-
1
def rewind(*args)
-
assert("rack.input#rewind called with arguments") { args.size == 0 }
-
assert("rack.input#rewind raised Errno::ESPIPE") {
-
begin
-
@input.rewind
-
true
-
rescue Errno::ESPIPE
-
false
-
end
-
}
-
end
-
-
## * +close+ must never be called on the input stream.
-
1
def close(*args)
-
assert("rack.input#close must not be called") { false }
-
end
-
end
-
-
## === The Error Stream
-
1
def check_error(error)
-
## The error stream must respond to +puts+, +write+ and +flush+.
-
2
[:puts, :write, :flush].each { |method|
-
6
assert("rack.error #{error} does not respond to ##{method}") {
-
6
error.respond_to? method
-
}
-
}
-
end
-
-
1
class ErrorWrapper
-
1
include Assertion
-
-
1
def initialize(error)
-
2
@error = error
-
end
-
-
## * +puts+ must be called with a single argument that responds to +to_s+.
-
1
def puts(str)
-
@error.puts str
-
end
-
-
## * +write+ must be called with a single argument that is a String.
-
1
def write(str)
-
assert("rack.errors#write not called with a String") { str.kind_of? String }
-
@error.write str
-
end
-
-
## * +flush+ must be called without arguments and must be called
-
## in order to make the error appear for sure.
-
1
def flush
-
@error.flush
-
end
-
-
## * +close+ must never be called on the error stream.
-
1
def close(*args)
-
assert("rack.errors#close must not be called") { false }
-
end
-
end
-
-
## == The Response
-
-
## === The Status
-
1
def check_status(status)
-
## This is an HTTP status. When parsed as integer (+to_i+), it must be
-
## greater than or equal to 100.
-
4
assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
-
end
-
-
## === The Headers
-
1
def check_headers(header)
-
## The header must respond to +each+, and yield values of key and value.
-
2
assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
-
2
header.respond_to? :each
-
}
-
2
header.each { |key, value|
-
## The header keys must be Strings.
-
4
assert("header key must be a string, was #{key.class}") {
-
4
key.kind_of? String
-
}
-
## The header must not contain a +Status+ key,
-
8
assert("header must not contain Status") { key.downcase != "status" }
-
## contain keys with <tt>:</tt> or newlines in their name,
-
8
assert("header names must not contain : or \\n") { key !~ /[:\n]/ }
-
## contain keys names that end in <tt>-</tt> or <tt>_</tt>,
-
8
assert("header names must not end in - or _") { key !~ /[-_]\z/ }
-
## but only contain keys that consist of
-
## letters, digits, <tt>_</tt> or <tt>-</tt> and start with a letter.
-
8
assert("invalid header name: #{key}") { key =~ /\A[a-zA-Z][a-zA-Z0-9_-]*\z/ }
-
-
## The values of the header must be Strings,
-
assert("a header value must be a String, but the value of " +
-
8
"'#{key}' is a #{value.class}") { value.kind_of? String }
-
## consisting of lines (for multiple header values, e.g. multiple
-
## <tt>Set-Cookie</tt> values) seperated by "\n".
-
4
value.split("\n").each { |item|
-
## The lines must not contain characters below 037.
-
4
assert("invalid header value #{key}: #{item.inspect}") {
-
4
item !~ /[\000-\037]/
-
}
-
}
-
}
-
end
-
-
## === The Content-Type
-
1
def check_content_type(status, headers)
-
2
headers.each { |key, value|
-
## There must be a <tt>Content-Type</tt>, except when the
-
## +Status+ is 1xx, 204, 205 or 304, in which case there must be none
-
## given.
-
2
if key.downcase == "content-type"
-
2
assert("Content-Type header found in #{status} response, not allowed") {
-
2
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
-
}
-
2
return
-
end
-
}
-
assert("No Content-Type header found") {
-
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
-
}
-
end
-
-
## === The Content-Length
-
1
def check_content_length(status, headers)
-
2
headers.each { |key, value|
-
4
if key.downcase == 'content-length'
-
## There must not be a <tt>Content-Length</tt> header when the
-
## +Status+ is 1xx, 204, 205 or 304.
-
2
assert("Content-Length header found in #{status} response, not allowed") {
-
2
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
-
}
-
2
@content_length = value
-
end
-
}
-
end
-
-
1
def verify_content_length(bytes)
-
2
if @head_request
-
assert("Response body was given for HEAD request, but should be empty") {
-
bytes == 0
-
}
-
2
elsif @content_length
-
2
assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
-
2
@content_length == bytes.to_s
-
}
-
end
-
end
-
-
## === The Body
-
1
def each
-
2
@closed = false
-
2
bytes = 0
-
-
## The Body must respond to +each+
-
2
assert("Response body must respond to each") do
-
2
@body.respond_to?(:each)
-
end
-
-
2
@body.each { |part|
-
## and must only yield String values.
-
2
assert("Body yielded non-string value #{part.inspect}") {
-
2
part.kind_of? String
-
}
-
2
bytes += Rack::Utils.bytesize(part)
-
2
yield part
-
}
-
2
verify_content_length(bytes)
-
-
##
-
## The Body itself should not be an instance of String, as this will
-
## break in Ruby 1.9.
-
##
-
## If the Body responds to +close+, it will be called after iteration.
-
# XXX howto: assert("Body has not been closed") { @closed }
-
-
-
##
-
## If the Body responds to +to_path+, it must return a String
-
## identifying the location of a file whose contents are identical
-
## to that produced by calling +each+; this may be used by the
-
## server as an alternative, possibly more efficient way to
-
## transport the response.
-
-
2
if @body.respond_to?(:to_path)
-
assert("The file identified by body.to_path does not exist") {
-
::File.exist? @body.to_path
-
}
-
end
-
-
##
-
## The Body commonly is an Array of Strings, the application
-
## instance itself, or a File-like object.
-
end
-
-
1
def close
-
2
@closed = true
-
2
@body.close if @body.respond_to?(:close)
-
end
-
-
# :startdoc:
-
-
end
-
end
-
-
## == Thanks
-
## Some parts of this specification are adopted from PEP333: Python
-
## Web Server Gateway Interface
-
## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank
-
## everyone involved in that effort.
-
1
module Rack
-
1
module Mime
-
# Returns String with mime type if found, otherwise use +fallback+.
-
# +ext+ should be filename extension in the '.ext' format that
-
# File.extname(file) returns.
-
# +fallback+ may be any object
-
#
-
# Also see the documentation for MIME_TYPES
-
#
-
# Usage:
-
# Rack::Mime.mime_type('.foo')
-
#
-
# This is a shortcut for:
-
# Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
-
-
1
def mime_type(ext, fallback='application/octet-stream')
-
32
MIME_TYPES.fetch(ext.to_s.downcase, fallback)
-
end
-
1
module_function :mime_type
-
-
# List of most common mime-types, selected various sources
-
# according to their usefulness in a webserving scope for Ruby
-
# users.
-
#
-
# To amend this list with your local mime.types list you can use:
-
#
-
# require 'webrick/httputils'
-
# list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
-
# Rack::Mime::MIME_TYPES.merge!(list)
-
#
-
# N.B. On Ubuntu the mime.types file does not include the leading period, so
-
# users may need to modify the data before merging into the hash.
-
#
-
# To add the list mongrel provides, use:
-
#
-
# require 'mongrel/handlers'
-
# Rack::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES)
-
-
1
MIME_TYPES = {
-
".123" => "application/vnd.lotus-1-2-3",
-
".3dml" => "text/vnd.in3d.3dml",
-
".3g2" => "video/3gpp2",
-
".3gp" => "video/3gpp",
-
".a" => "application/octet-stream",
-
".acc" => "application/vnd.americandynamics.acc",
-
".ace" => "application/x-ace-compressed",
-
".acu" => "application/vnd.acucobol",
-
".aep" => "application/vnd.audiograph",
-
".afp" => "application/vnd.ibm.modcap",
-
".ai" => "application/postscript",
-
".aif" => "audio/x-aiff",
-
".aiff" => "audio/x-aiff",
-
".ami" => "application/vnd.amiga.ami",
-
".appcache" => "text/cache-manifest",
-
".apr" => "application/vnd.lotus-approach",
-
".asc" => "application/pgp-signature",
-
".asf" => "video/x-ms-asf",
-
".asm" => "text/x-asm",
-
".aso" => "application/vnd.accpac.simply.aso",
-
".asx" => "video/x-ms-asf",
-
".atc" => "application/vnd.acucorp",
-
".atom" => "application/atom+xml",
-
".atomcat" => "application/atomcat+xml",
-
".atomsvc" => "application/atomsvc+xml",
-
".atx" => "application/vnd.antix.game-component",
-
".au" => "audio/basic",
-
".avi" => "video/x-msvideo",
-
".bat" => "application/x-msdownload",
-
".bcpio" => "application/x-bcpio",
-
".bdm" => "application/vnd.syncml.dm+wbxml",
-
".bh2" => "application/vnd.fujitsu.oasysprs",
-
".bin" => "application/octet-stream",
-
".bmi" => "application/vnd.bmi",
-
".bmp" => "image/bmp",
-
".box" => "application/vnd.previewsystems.box",
-
".btif" => "image/prs.btif",
-
".bz" => "application/x-bzip",
-
".bz2" => "application/x-bzip2",
-
".c" => "text/x-c",
-
".c4g" => "application/vnd.clonk.c4group",
-
".cab" => "application/vnd.ms-cab-compressed",
-
".cc" => "text/x-c",
-
".ccxml" => "application/ccxml+xml",
-
".cdbcmsg" => "application/vnd.contact.cmsg",
-
".cdkey" => "application/vnd.mediastation.cdkey",
-
".cdx" => "chemical/x-cdx",
-
".cdxml" => "application/vnd.chemdraw+xml",
-
".cdy" => "application/vnd.cinderella",
-
".cer" => "application/pkix-cert",
-
".cgm" => "image/cgm",
-
".chat" => "application/x-chat",
-
".chm" => "application/vnd.ms-htmlhelp",
-
".chrt" => "application/vnd.kde.kchart",
-
".cif" => "chemical/x-cif",
-
".cii" => "application/vnd.anser-web-certificate-issue-initiation",
-
".cil" => "application/vnd.ms-artgalry",
-
".cla" => "application/vnd.claymore",
-
".class" => "application/octet-stream",
-
".clkk" => "application/vnd.crick.clicker.keyboard",
-
".clkp" => "application/vnd.crick.clicker.palette",
-
".clkt" => "application/vnd.crick.clicker.template",
-
".clkw" => "application/vnd.crick.clicker.wordbank",
-
".clkx" => "application/vnd.crick.clicker",
-
".clp" => "application/x-msclip",
-
".cmc" => "application/vnd.cosmocaller",
-
".cmdf" => "chemical/x-cmdf",
-
".cml" => "chemical/x-cml",
-
".cmp" => "application/vnd.yellowriver-custom-menu",
-
".cmx" => "image/x-cmx",
-
".com" => "application/x-msdownload",
-
".conf" => "text/plain",
-
".cpio" => "application/x-cpio",
-
".cpp" => "text/x-c",
-
".cpt" => "application/mac-compactpro",
-
".crd" => "application/x-mscardfile",
-
".crl" => "application/pkix-crl",
-
".crt" => "application/x-x509-ca-cert",
-
".csh" => "application/x-csh",
-
".csml" => "chemical/x-csml",
-
".csp" => "application/vnd.commonspace",
-
".css" => "text/css",
-
".csv" => "text/csv",
-
".curl" => "application/vnd.curl",
-
".cww" => "application/prs.cww",
-
".cxx" => "text/x-c",
-
".daf" => "application/vnd.mobius.daf",
-
".davmount" => "application/davmount+xml",
-
".dcr" => "application/x-director",
-
".dd2" => "application/vnd.oma.dd2+xml",
-
".ddd" => "application/vnd.fujixerox.ddd",
-
".deb" => "application/x-debian-package",
-
".der" => "application/x-x509-ca-cert",
-
".dfac" => "application/vnd.dreamfactory",
-
".diff" => "text/x-diff",
-
".dis" => "application/vnd.mobius.dis",
-
".djv" => "image/vnd.djvu",
-
".djvu" => "image/vnd.djvu",
-
".dll" => "application/x-msdownload",
-
".dmg" => "application/octet-stream",
-
".dna" => "application/vnd.dna",
-
".doc" => "application/msword",
-
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
-
".dot" => "application/msword",
-
".dp" => "application/vnd.osgi.dp",
-
".dpg" => "application/vnd.dpgraph",
-
".dsc" => "text/prs.lines.tag",
-
".dtd" => "application/xml-dtd",
-
".dts" => "audio/vnd.dts",
-
".dtshd" => "audio/vnd.dts.hd",
-
".dv" => "video/x-dv",
-
".dvi" => "application/x-dvi",
-
".dwf" => "model/vnd.dwf",
-
".dwg" => "image/vnd.dwg",
-
".dxf" => "image/vnd.dxf",
-
".dxp" => "application/vnd.spotfire.dxp",
-
".ear" => "application/java-archive",
-
".ecelp4800" => "audio/vnd.nuera.ecelp4800",
-
".ecelp7470" => "audio/vnd.nuera.ecelp7470",
-
".ecelp9600" => "audio/vnd.nuera.ecelp9600",
-
".ecma" => "application/ecmascript",
-
".edm" => "application/vnd.novadigm.edm",
-
".edx" => "application/vnd.novadigm.edx",
-
".efif" => "application/vnd.picsel",
-
".ei6" => "application/vnd.pg.osasli",
-
".eml" => "message/rfc822",
-
".eol" => "audio/vnd.digital-winds",
-
".eot" => "application/vnd.ms-fontobject",
-
".eps" => "application/postscript",
-
".es3" => "application/vnd.eszigno3+xml",
-
".esf" => "application/vnd.epson.esf",
-
".etx" => "text/x-setext",
-
".exe" => "application/x-msdownload",
-
".ext" => "application/vnd.novadigm.ext",
-
".ez" => "application/andrew-inset",
-
".ez2" => "application/vnd.ezpix-album",
-
".ez3" => "application/vnd.ezpix-package",
-
".f" => "text/x-fortran",
-
".f77" => "text/x-fortran",
-
".f90" => "text/x-fortran",
-
".fbs" => "image/vnd.fastbidsheet",
-
".fdf" => "application/vnd.fdf",
-
".fe_launch" => "application/vnd.denovo.fcselayout-link",
-
".fg5" => "application/vnd.fujitsu.oasysgp",
-
".fli" => "video/x-fli",
-
".flo" => "application/vnd.micrografx.flo",
-
".flv" => "video/x-flv",
-
".flw" => "application/vnd.kde.kivio",
-
".flx" => "text/vnd.fmi.flexstor",
-
".fly" => "text/vnd.fly",
-
".fm" => "application/vnd.framemaker",
-
".fnc" => "application/vnd.frogans.fnc",
-
".for" => "text/x-fortran",
-
".fpx" => "image/vnd.fpx",
-
".fsc" => "application/vnd.fsc.weblaunch",
-
".fst" => "image/vnd.fst",
-
".ftc" => "application/vnd.fluxtime.clip",
-
".fti" => "application/vnd.anser-web-funds-transfer-initiation",
-
".fvt" => "video/vnd.fvt",
-
".fzs" => "application/vnd.fuzzysheet",
-
".g3" => "image/g3fax",
-
".gac" => "application/vnd.groove-account",
-
".gdl" => "model/vnd.gdl",
-
".gem" => "application/octet-stream",
-
".gemspec" => "text/x-script.ruby",
-
".ghf" => "application/vnd.groove-help",
-
".gif" => "image/gif",
-
".gim" => "application/vnd.groove-identity-message",
-
".gmx" => "application/vnd.gmx",
-
".gph" => "application/vnd.flographit",
-
".gqf" => "application/vnd.grafeq",
-
".gram" => "application/srgs",
-
".grv" => "application/vnd.groove-injector",
-
".grxml" => "application/srgs+xml",
-
".gtar" => "application/x-gtar",
-
".gtm" => "application/vnd.groove-tool-message",
-
".gtw" => "model/vnd.gtw",
-
".gv" => "text/vnd.graphviz",
-
".gz" => "application/x-gzip",
-
".h" => "text/x-c",
-
".h261" => "video/h261",
-
".h263" => "video/h263",
-
".h264" => "video/h264",
-
".hbci" => "application/vnd.hbci",
-
".hdf" => "application/x-hdf",
-
".hh" => "text/x-c",
-
".hlp" => "application/winhlp",
-
".hpgl" => "application/vnd.hp-hpgl",
-
".hpid" => "application/vnd.hp-hpid",
-
".hps" => "application/vnd.hp-hps",
-
".hqx" => "application/mac-binhex40",
-
".htc" => "text/x-component",
-
".htke" => "application/vnd.kenameaapp",
-
".htm" => "text/html",
-
".html" => "text/html",
-
".hvd" => "application/vnd.yamaha.hv-dic",
-
".hvp" => "application/vnd.yamaha.hv-voice",
-
".hvs" => "application/vnd.yamaha.hv-script",
-
".icc" => "application/vnd.iccprofile",
-
".ice" => "x-conference/x-cooltalk",
-
".ico" => "image/vnd.microsoft.icon",
-
".ics" => "text/calendar",
-
".ief" => "image/ief",
-
".ifb" => "text/calendar",
-
".ifm" => "application/vnd.shana.informed.formdata",
-
".igl" => "application/vnd.igloader",
-
".igs" => "model/iges",
-
".igx" => "application/vnd.micrografx.igx",
-
".iif" => "application/vnd.shana.informed.interchange",
-
".imp" => "application/vnd.accpac.simply.imp",
-
".ims" => "application/vnd.ms-ims",
-
".ipk" => "application/vnd.shana.informed.package",
-
".irm" => "application/vnd.ibm.rights-management",
-
".irp" => "application/vnd.irepository.package+xml",
-
".iso" => "application/octet-stream",
-
".itp" => "application/vnd.shana.informed.formtemplate",
-
".ivp" => "application/vnd.immervision-ivp",
-
".ivu" => "application/vnd.immervision-ivu",
-
".jad" => "text/vnd.sun.j2me.app-descriptor",
-
".jam" => "application/vnd.jam",
-
".jar" => "application/java-archive",
-
".java" => "text/x-java-source",
-
".jisp" => "application/vnd.jisp",
-
".jlt" => "application/vnd.hp-jlyt",
-
".jnlp" => "application/x-java-jnlp-file",
-
".joda" => "application/vnd.joost.joda-archive",
-
".jp2" => "image/jp2",
-
".jpeg" => "image/jpeg",
-
".jpg" => "image/jpeg",
-
".jpgv" => "video/jpeg",
-
".jpm" => "video/jpm",
-
".js" => "application/javascript",
-
".json" => "application/json",
-
".karbon" => "application/vnd.kde.karbon",
-
".kfo" => "application/vnd.kde.kformula",
-
".kia" => "application/vnd.kidspiration",
-
".kml" => "application/vnd.google-earth.kml+xml",
-
".kmz" => "application/vnd.google-earth.kmz",
-
".kne" => "application/vnd.kinar",
-
".kon" => "application/vnd.kde.kontour",
-
".kpr" => "application/vnd.kde.kpresenter",
-
".ksp" => "application/vnd.kde.kspread",
-
".ktz" => "application/vnd.kahootz",
-
".kwd" => "application/vnd.kde.kword",
-
".latex" => "application/x-latex",
-
".lbd" => "application/vnd.llamagraphics.life-balance.desktop",
-
".lbe" => "application/vnd.llamagraphics.life-balance.exchange+xml",
-
".les" => "application/vnd.hhe.lesson-player",
-
".link66" => "application/vnd.route66.link66+xml",
-
".log" => "text/plain",
-
".lostxml" => "application/lost+xml",
-
".lrm" => "application/vnd.ms-lrm",
-
".ltf" => "application/vnd.frogans.ltf",
-
".lvp" => "audio/vnd.lucent.voice",
-
".lwp" => "application/vnd.lotus-wordpro",
-
".m3u" => "audio/x-mpegurl",
-
".m4a" => "audio/mp4a-latm",
-
".m4v" => "video/mp4",
-
".ma" => "application/mathematica",
-
".mag" => "application/vnd.ecowin.chart",
-
".man" => "text/troff",
-
".manifest" => "text/cache-manifest",
-
".mathml" => "application/mathml+xml",
-
".mbk" => "application/vnd.mobius.mbk",
-
".mbox" => "application/mbox",
-
".mc1" => "application/vnd.medcalcdata",
-
".mcd" => "application/vnd.mcd",
-
".mdb" => "application/x-msaccess",
-
".mdi" => "image/vnd.ms-modi",
-
".mdoc" => "text/troff",
-
".me" => "text/troff",
-
".mfm" => "application/vnd.mfmp",
-
".mgz" => "application/vnd.proteus.magazine",
-
".mid" => "audio/midi",
-
".midi" => "audio/midi",
-
".mif" => "application/vnd.mif",
-
".mime" => "message/rfc822",
-
".mj2" => "video/mj2",
-
".mlp" => "application/vnd.dolby.mlp",
-
".mmd" => "application/vnd.chipnuts.karaoke-mmd",
-
".mmf" => "application/vnd.smaf",
-
".mml" => "application/mathml+xml",
-
".mmr" => "image/vnd.fujixerox.edmics-mmr",
-
".mng" => "video/x-mng",
-
".mny" => "application/x-msmoney",
-
".mov" => "video/quicktime",
-
".movie" => "video/x-sgi-movie",
-
".mp3" => "audio/mpeg",
-
".mp4" => "video/mp4",
-
".mp4a" => "audio/mp4",
-
".mp4s" => "application/mp4",
-
".mp4v" => "video/mp4",
-
".mpc" => "application/vnd.mophun.certificate",
-
".mpeg" => "video/mpeg",
-
".mpg" => "video/mpeg",
-
".mpga" => "audio/mpeg",
-
".mpkg" => "application/vnd.apple.installer+xml",
-
".mpm" => "application/vnd.blueice.multipass",
-
".mpn" => "application/vnd.mophun.application",
-
".mpp" => "application/vnd.ms-project",
-
".mpy" => "application/vnd.ibm.minipay",
-
".mqy" => "application/vnd.mobius.mqy",
-
".mrc" => "application/marc",
-
".ms" => "text/troff",
-
".mscml" => "application/mediaservercontrol+xml",
-
".mseq" => "application/vnd.mseq",
-
".msf" => "application/vnd.epson.msf",
-
".msh" => "model/mesh",
-
".msi" => "application/x-msdownload",
-
".msl" => "application/vnd.mobius.msl",
-
".msty" => "application/vnd.muvee.style",
-
".mts" => "model/vnd.mts",
-
".mus" => "application/vnd.musician",
-
".mvb" => "application/x-msmediaview",
-
".mwf" => "application/vnd.mfer",
-
".mxf" => "application/mxf",
-
".mxl" => "application/vnd.recordare.musicxml",
-
".mxml" => "application/xv+xml",
-
".mxs" => "application/vnd.triscape.mxs",
-
".mxu" => "video/vnd.mpegurl",
-
".n" => "application/vnd.nokia.n-gage.symbian.install",
-
".nc" => "application/x-netcdf",
-
".ngdat" => "application/vnd.nokia.n-gage.data",
-
".nlu" => "application/vnd.neurolanguage.nlu",
-
".nml" => "application/vnd.enliven",
-
".nnd" => "application/vnd.noblenet-directory",
-
".nns" => "application/vnd.noblenet-sealer",
-
".nnw" => "application/vnd.noblenet-web",
-
".npx" => "image/vnd.net-fpx",
-
".nsf" => "application/vnd.lotus-notes",
-
".oa2" => "application/vnd.fujitsu.oasys2",
-
".oa3" => "application/vnd.fujitsu.oasys3",
-
".oas" => "application/vnd.fujitsu.oasys",
-
".obd" => "application/x-msbinder",
-
".oda" => "application/oda",
-
".odc" => "application/vnd.oasis.opendocument.chart",
-
".odf" => "application/vnd.oasis.opendocument.formula",
-
".odg" => "application/vnd.oasis.opendocument.graphics",
-
".odi" => "application/vnd.oasis.opendocument.image",
-
".odp" => "application/vnd.oasis.opendocument.presentation",
-
".ods" => "application/vnd.oasis.opendocument.spreadsheet",
-
".odt" => "application/vnd.oasis.opendocument.text",
-
".oga" => "audio/ogg",
-
".ogg" => "application/ogg",
-
".ogv" => "video/ogg",
-
".ogx" => "application/ogg",
-
".org" => "application/vnd.lotus-organizer",
-
".otc" => "application/vnd.oasis.opendocument.chart-template",
-
".otf" => "application/vnd.oasis.opendocument.formula-template",
-
".otg" => "application/vnd.oasis.opendocument.graphics-template",
-
".oth" => "application/vnd.oasis.opendocument.text-web",
-
".oti" => "application/vnd.oasis.opendocument.image-template",
-
".otm" => "application/vnd.oasis.opendocument.text-master",
-
".ots" => "application/vnd.oasis.opendocument.spreadsheet-template",
-
".ott" => "application/vnd.oasis.opendocument.text-template",
-
".oxt" => "application/vnd.openofficeorg.extension",
-
".p" => "text/x-pascal",
-
".p10" => "application/pkcs10",
-
".p12" => "application/x-pkcs12",
-
".p7b" => "application/x-pkcs7-certificates",
-
".p7m" => "application/pkcs7-mime",
-
".p7r" => "application/x-pkcs7-certreqresp",
-
".p7s" => "application/pkcs7-signature",
-
".pas" => "text/x-pascal",
-
".pbd" => "application/vnd.powerbuilder6",
-
".pbm" => "image/x-portable-bitmap",
-
".pcl" => "application/vnd.hp-pcl",
-
".pclxl" => "application/vnd.hp-pclxl",
-
".pcx" => "image/x-pcx",
-
".pdb" => "chemical/x-pdb",
-
".pdf" => "application/pdf",
-
".pem" => "application/x-x509-ca-cert",
-
".pfr" => "application/font-tdpfr",
-
".pgm" => "image/x-portable-graymap",
-
".pgn" => "application/x-chess-pgn",
-
".pgp" => "application/pgp-encrypted",
-
".pic" => "image/x-pict",
-
".pict" => "image/pict",
-
".pkg" => "application/octet-stream",
-
".pki" => "application/pkixcmp",
-
".pkipath" => "application/pkix-pkipath",
-
".pl" => "text/x-script.perl",
-
".plb" => "application/vnd.3gpp.pic-bw-large",
-
".plc" => "application/vnd.mobius.plc",
-
".plf" => "application/vnd.pocketlearn",
-
".pls" => "application/pls+xml",
-
".pm" => "text/x-script.perl-module",
-
".pml" => "application/vnd.ctc-posml",
-
".png" => "image/png",
-
".pnm" => "image/x-portable-anymap",
-
".pntg" => "image/x-macpaint",
-
".portpkg" => "application/vnd.macports.portpkg",
-
".ppd" => "application/vnd.cups-ppd",
-
".ppm" => "image/x-portable-pixmap",
-
".pps" => "application/vnd.ms-powerpoint",
-
".ppt" => "application/vnd.ms-powerpoint",
-
".prc" => "application/vnd.palm",
-
".pre" => "application/vnd.lotus-freelance",
-
".prf" => "application/pics-rules",
-
".ps" => "application/postscript",
-
".psb" => "application/vnd.3gpp.pic-bw-small",
-
".psd" => "image/vnd.adobe.photoshop",
-
".ptid" => "application/vnd.pvi.ptid1",
-
".pub" => "application/x-mspublisher",
-
".pvb" => "application/vnd.3gpp.pic-bw-var",
-
".pwn" => "application/vnd.3m.post-it-notes",
-
".py" => "text/x-script.python",
-
".pya" => "audio/vnd.ms-playready.media.pya",
-
".pyv" => "video/vnd.ms-playready.media.pyv",
-
".qam" => "application/vnd.epson.quickanime",
-
".qbo" => "application/vnd.intu.qbo",
-
".qfx" => "application/vnd.intu.qfx",
-
".qps" => "application/vnd.publishare-delta-tree",
-
".qt" => "video/quicktime",
-
".qtif" => "image/x-quicktime",
-
".qxd" => "application/vnd.quark.quarkxpress",
-
".ra" => "audio/x-pn-realaudio",
-
".rake" => "text/x-script.ruby",
-
".ram" => "audio/x-pn-realaudio",
-
".rar" => "application/x-rar-compressed",
-
".ras" => "image/x-cmu-raster",
-
".rb" => "text/x-script.ruby",
-
".rcprofile" => "application/vnd.ipunplugged.rcprofile",
-
".rdf" => "application/rdf+xml",
-
".rdz" => "application/vnd.data-vision.rdz",
-
".rep" => "application/vnd.businessobjects",
-
".rgb" => "image/x-rgb",
-
".rif" => "application/reginfo+xml",
-
".rl" => "application/resource-lists+xml",
-
".rlc" => "image/vnd.fujixerox.edmics-rlc",
-
".rld" => "application/resource-lists-diff+xml",
-
".rm" => "application/vnd.rn-realmedia",
-
".rmp" => "audio/x-pn-realaudio-plugin",
-
".rms" => "application/vnd.jcp.javame.midlet-rms",
-
".rnc" => "application/relax-ng-compact-syntax",
-
".roff" => "text/troff",
-
".rpm" => "application/x-redhat-package-manager",
-
".rpss" => "application/vnd.nokia.radio-presets",
-
".rpst" => "application/vnd.nokia.radio-preset",
-
".rq" => "application/sparql-query",
-
".rs" => "application/rls-services+xml",
-
".rsd" => "application/rsd+xml",
-
".rss" => "application/rss+xml",
-
".rtf" => "application/rtf",
-
".rtx" => "text/richtext",
-
".ru" => "text/x-script.ruby",
-
".s" => "text/x-asm",
-
".saf" => "application/vnd.yamaha.smaf-audio",
-
".sbml" => "application/sbml+xml",
-
".sc" => "application/vnd.ibm.secure-container",
-
".scd" => "application/x-msschedule",
-
".scm" => "application/vnd.lotus-screencam",
-
".scq" => "application/scvp-cv-request",
-
".scs" => "application/scvp-cv-response",
-
".sdkm" => "application/vnd.solent.sdkm+xml",
-
".sdp" => "application/sdp",
-
".see" => "application/vnd.seemail",
-
".sema" => "application/vnd.sema",
-
".semd" => "application/vnd.semd",
-
".semf" => "application/vnd.semf",
-
".setpay" => "application/set-payment-initiation",
-
".setreg" => "application/set-registration-initiation",
-
".sfd" => "application/vnd.hydrostatix.sof-data",
-
".sfs" => "application/vnd.spotfire.sfs",
-
".sgm" => "text/sgml",
-
".sgml" => "text/sgml",
-
".sh" => "application/x-sh",
-
".shar" => "application/x-shar",
-
".shf" => "application/shf+xml",
-
".sig" => "application/pgp-signature",
-
".sit" => "application/x-stuffit",
-
".sitx" => "application/x-stuffitx",
-
".skp" => "application/vnd.koan",
-
".slt" => "application/vnd.epson.salt",
-
".smi" => "application/smil+xml",
-
".snd" => "audio/basic",
-
".so" => "application/octet-stream",
-
".spf" => "application/vnd.yamaha.smaf-phrase",
-
".spl" => "application/x-futuresplash",
-
".spot" => "text/vnd.in3d.spot",
-
".spp" => "application/scvp-vp-response",
-
".spq" => "application/scvp-vp-request",
-
".src" => "application/x-wais-source",
-
".srx" => "application/sparql-results+xml",
-
".sse" => "application/vnd.kodak-descriptor",
-
".ssf" => "application/vnd.epson.ssf",
-
".ssml" => "application/ssml+xml",
-
".stf" => "application/vnd.wt.stf",
-
".stk" => "application/hyperstudio",
-
".str" => "application/vnd.pg.format",
-
".sus" => "application/vnd.sus-calendar",
-
".sv4cpio" => "application/x-sv4cpio",
-
".sv4crc" => "application/x-sv4crc",
-
".svd" => "application/vnd.svd",
-
".svg" => "image/svg+xml",
-
".svgz" => "image/svg+xml",
-
".swf" => "application/x-shockwave-flash",
-
".swi" => "application/vnd.arastra.swi",
-
".t" => "text/troff",
-
".tao" => "application/vnd.tao.intent-module-archive",
-
".tar" => "application/x-tar",
-
".tbz" => "application/x-bzip-compressed-tar",
-
".tcap" => "application/vnd.3gpp2.tcap",
-
".tcl" => "application/x-tcl",
-
".tex" => "application/x-tex",
-
".texi" => "application/x-texinfo",
-
".texinfo" => "application/x-texinfo",
-
".text" => "text/plain",
-
".tif" => "image/tiff",
-
".tiff" => "image/tiff",
-
".tmo" => "application/vnd.tmobile-livetv",
-
".torrent" => "application/x-bittorrent",
-
".tpl" => "application/vnd.groove-tool-template",
-
".tpt" => "application/vnd.trid.tpt",
-
".tr" => "text/troff",
-
".tra" => "application/vnd.trueapp",
-
".trm" => "application/x-msterminal",
-
".tsv" => "text/tab-separated-values",
-
".ttf" => "application/octet-stream",
-
".twd" => "application/vnd.simtech-mindmapper",
-
".txd" => "application/vnd.genomatix.tuxedo",
-
".txf" => "application/vnd.mobius.txf",
-
".txt" => "text/plain",
-
".ufd" => "application/vnd.ufdl",
-
".umj" => "application/vnd.umajin",
-
".unityweb" => "application/vnd.unity",
-
".uoml" => "application/vnd.uoml+xml",
-
".uri" => "text/uri-list",
-
".ustar" => "application/x-ustar",
-
".utz" => "application/vnd.uiq.theme",
-
".uu" => "text/x-uuencode",
-
".vcd" => "application/x-cdlink",
-
".vcf" => "text/x-vcard",
-
".vcg" => "application/vnd.groove-vcard",
-
".vcs" => "text/x-vcalendar",
-
".vcx" => "application/vnd.vcx",
-
".vis" => "application/vnd.visionary",
-
".viv" => "video/vnd.vivo",
-
".vrml" => "model/vrml",
-
".vsd" => "application/vnd.visio",
-
".vsf" => "application/vnd.vsf",
-
".vtu" => "model/vnd.vtu",
-
".vxml" => "application/voicexml+xml",
-
".war" => "application/java-archive",
-
".wav" => "audio/x-wav",
-
".wax" => "audio/x-ms-wax",
-
".wbmp" => "image/vnd.wap.wbmp",
-
".wbs" => "application/vnd.criticaltools.wbs+xml",
-
".wbxml" => "application/vnd.wap.wbxml",
-
".webm" => "video/webm",
-
".wm" => "video/x-ms-wm",
-
".wma" => "audio/x-ms-wma",
-
".wmd" => "application/x-ms-wmd",
-
".wmf" => "application/x-msmetafile",
-
".wml" => "text/vnd.wap.wml",
-
".wmlc" => "application/vnd.wap.wmlc",
-
".wmls" => "text/vnd.wap.wmlscript",
-
".wmlsc" => "application/vnd.wap.wmlscriptc",
-
".wmv" => "video/x-ms-wmv",
-
".wmx" => "video/x-ms-wmx",
-
".wmz" => "application/x-ms-wmz",
-
".woff" => "application/octet-stream",
-
".wpd" => "application/vnd.wordperfect",
-
".wpl" => "application/vnd.ms-wpl",
-
".wps" => "application/vnd.ms-works",
-
".wqd" => "application/vnd.wqd",
-
".wri" => "application/x-mswrite",
-
".wrl" => "model/vrml",
-
".wsdl" => "application/wsdl+xml",
-
".wspolicy" => "application/wspolicy+xml",
-
".wtb" => "application/vnd.webturbo",
-
".wvx" => "video/x-ms-wvx",
-
".x3d" => "application/vnd.hzn-3d-crossword",
-
".xar" => "application/vnd.xara",
-
".xbd" => "application/vnd.fujixerox.docuworks.binder",
-
".xbm" => "image/x-xbitmap",
-
".xdm" => "application/vnd.syncml.dm+xml",
-
".xdp" => "application/vnd.adobe.xdp+xml",
-
".xdw" => "application/vnd.fujixerox.docuworks",
-
".xenc" => "application/xenc+xml",
-
".xer" => "application/patch-ops-error+xml",
-
".xfdf" => "application/vnd.adobe.xfdf",
-
".xfdl" => "application/vnd.xfdl",
-
".xhtml" => "application/xhtml+xml",
-
".xif" => "image/vnd.xiff",
-
".xls" => "application/vnd.ms-excel",
-
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
-
".xml" => "application/xml",
-
".xo" => "application/vnd.olpc-sugar",
-
".xop" => "application/xop+xml",
-
".xpm" => "image/x-xpixmap",
-
".xpr" => "application/vnd.is-xpr",
-
".xps" => "application/vnd.ms-xpsdocument",
-
".xpw" => "application/vnd.intercon.formnet",
-
".xsl" => "application/xml",
-
".xslt" => "application/xslt+xml",
-
".xsm" => "application/vnd.syncml+xml",
-
".xspf" => "application/xspf+xml",
-
".xul" => "application/vnd.mozilla.xul+xml",
-
".xwd" => "image/x-xwindowdump",
-
".xyz" => "chemical/x-xyz",
-
".yaml" => "text/yaml",
-
".yml" => "text/yaml",
-
".zaz" => "application/vnd.zzazz.deck+xml",
-
".zip" => "application/zip",
-
".zmm" => "application/vnd.handheld-entertainment+xml",
-
}
-
end
-
end
-
1
require 'uri'
-
1
require 'stringio'
-
1
require 'rack'
-
1
require 'rack/lint'
-
1
require 'rack/utils'
-
1
require 'rack/response'
-
-
1
module Rack
-
# Rack::MockRequest helps testing your Rack application without
-
# actually using HTTP.
-
#
-
# After performing a request on a URL with get/post/put/delete, it
-
# returns a MockResponse with useful helper methods for effective
-
# testing.
-
#
-
# You can pass a hash with additional configuration to the
-
# get/post/put/delete.
-
# <tt>:input</tt>:: A String or IO-like to be used as rack.input.
-
# <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
-
# <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
-
-
1
class MockRequest
-
1
class FatalWarning < RuntimeError
-
end
-
-
1
class FatalWarner
-
1
def puts(warning)
-
raise FatalWarning, warning
-
end
-
-
1
def write(warning)
-
raise FatalWarning, warning
-
end
-
-
1
def flush
-
end
-
-
1
def string
-
""
-
end
-
end
-
-
1
DEFAULT_ENV = {
-
"rack.version" => Rack::VERSION,
-
"rack.input" => StringIO.new,
-
"rack.errors" => StringIO.new,
-
"rack.multithread" => true,
-
"rack.multiprocess" => true,
-
"rack.run_once" => false,
-
}
-
-
1
def initialize(app)
-
36
@app = app
-
end
-
-
1
def get(uri, opts={}) request("GET", uri, opts) end
-
3
def post(uri, opts={}) request("POST", uri, opts) end
-
1
def put(uri, opts={}) request("PUT", uri, opts) end
-
1
def delete(uri, opts={}) request("DELETE", uri, opts) end
-
1
def head(uri, opts={}) request("HEAD", uri, opts) end
-
-
1
def request(method="GET", uri="", opts={})
-
36
env = self.class.env_for(uri, opts.merge(:method => method))
-
-
36
if opts[:lint]
-
2
app = Rack::Lint.new(@app)
-
else
-
34
app = @app
-
end
-
-
36
errors = env["rack.errors"]
-
36
status, headers, body = app.call(env)
-
36
MockResponse.new(status, headers, body, errors)
-
ensure
-
36
body.close if body.respond_to?(:close)
-
end
-
-
# Return the Rack environment used for a request to +uri+.
-
1
def self.env_for(uri="", opts={})
-
2845
uri = URI(uri)
-
2844
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
-
-
2844
env = DEFAULT_ENV.dup
-
-
2844
env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET"
-
2844
env["SERVER_NAME"] = uri.host || "example.org"
-
2844
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
-
2844
env["QUERY_STRING"] = uri.query.to_s
-
2844
env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
-
2844
env["rack.url_scheme"] = uri.scheme || "http"
-
2844
env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
-
-
2844
env["SCRIPT_NAME"] = opts[:script_name] || ""
-
-
2844
if opts[:fatal]
-
env["rack.errors"] = FatalWarner.new
-
else
-
2844
env["rack.errors"] = StringIO.new
-
end
-
-
2844
if params = opts[:params]
-
if env["REQUEST_METHOD"] == "GET"
-
params = Utils.parse_nested_query(params) if params.is_a?(String)
-
params.update(Utils.parse_nested_query(env["QUERY_STRING"]))
-
env["QUERY_STRING"] = Utils.build_nested_query(params)
-
elsif !opts.has_key?(:input)
-
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
-
if params.is_a?(Hash)
-
if data = Utils::Multipart.build_multipart(params)
-
opts[:input] = data
-
opts["CONTENT_LENGTH"] ||= data.length.to_s
-
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}"
-
else
-
opts[:input] = Utils.build_nested_query(params)
-
end
-
else
-
opts[:input] = params
-
end
-
end
-
end
-
-
2844
empty_str = ""
-
2844
empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
-
2844
opts[:input] ||= empty_str
-
2844
if String === opts[:input]
-
2844
rack_input = StringIO.new(opts[:input])
-
else
-
rack_input = opts[:input]
-
end
-
-
2844
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
-
2844
env['rack.input'] = rack_input
-
-
2844
env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
-
-
2844
opts.each { |field, value|
-
14362
env[field] = value if String === field
-
}
-
-
2844
env
-
end
-
end
-
-
# Rack::MockResponse provides useful helpers for testing your apps.
-
# Usually, you don't create the MockResponse on your own, but use
-
# MockRequest.
-
-
1
class MockResponse < Rack::Response
-
# Headers
-
1
attr_reader :original_headers
-
-
# Errors
-
1
attr_accessor :errors
-
-
1
def initialize(status, headers, body, errors=StringIO.new(""))
-
809
@original_headers = headers
-
809
@errors = errors.string if errors.respond_to?(:string)
-
809
@body_string = nil
-
-
809
super(body, status, headers)
-
end
-
-
1
def =~(other)
-
body =~ other
-
end
-
-
1
def match(other)
-
body.match other
-
end
-
-
1
def body
-
# FIXME: apparently users of MockResponse expect the return value of
-
# MockResponse#body to be a string. However, the real response object
-
# returns the body as a list.
-
#
-
# See spec_showstatus.rb:
-
#
-
# should "not replace existing messages" do
-
# ...
-
# res.body.should == "foo!"
-
# end
-
809
super.join
-
end
-
-
1
def empty?
-
[201, 204, 205, 304].include? status
-
end
-
end
-
end
-
1
module Rack
-
# A multipart form data parser, adapted from IOWA.
-
#
-
# Usually, Rack::Request#POST takes care of calling this.
-
1
module Multipart
-
1
autoload :UploadedFile, 'rack/multipart/uploaded_file'
-
1
autoload :Parser, 'rack/multipart/parser'
-
1
autoload :Generator, 'rack/multipart/generator'
-
-
1
EOL = "\r\n"
-
1
MULTIPART_BOUNDARY = "AaB03x"
-
1
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
-
1
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
-
1
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
-
1
DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})*/
-
1
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
-
1
BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
-
1
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
-
1
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
-
1
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name="?([^\";]*)"?/ni
-
1
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
-
-
1
class << self
-
1
def parse_multipart(env)
-
330
Parser.new(env).parse
-
end
-
-
1
def build_multipart(params, first = true)
-
Generator.new(params, first).dump
-
end
-
end
-
-
end
-
end
-
1
require 'rack/utils'
-
-
1
module Rack
-
1
module Multipart
-
1
class Parser
-
1
BUFSIZE = 16384
-
-
1
def initialize(env)
-
330
@env = env
-
end
-
-
1
def parse
-
330
return nil unless setup_parse
-
-
11
fast_forward_to_first_boundary
-
-
11
loop do
-
19
head, filename, content_type, name, body =
-
get_current_head_and_filename_and_content_type_and_name_and_body
-
-
# Save the rest.
-
19
if i = @buf.index(rx)
-
19
body << @buf.slice!(0, i)
-
19
@buf.slice!(0, @boundary_size+2)
-
-
19
@content_length = -1 if $1 == "--"
-
end
-
-
19
filename, data = get_data(filename, body, content_type, name, head)
-
-
19
Utils.normalize_params(@params, name, data) unless data.nil?
-
-
# break if we're at the end of a buffer, but not if it is the end of a field
-
19
break if (@buf.empty? && $1 != EOL) || @content_length == -1
-
end
-
-
11
@io.rewind
-
-
11
@params.to_params_hash
-
end
-
-
1
private
-
1
def setup_parse
-
330
return false unless @env['CONTENT_TYPE'] =~ MULTIPART
-
-
11
@boundary = "--#{$1}"
-
-
11
@buf = ""
-
11
@params = Utils::KeySpaceConstrainedParams.new
-
-
11
@content_length = @env['CONTENT_LENGTH'].to_i
-
11
@io = @env['rack.input']
-
11
@io.rewind
-
-
11
@boundary_size = Utils.bytesize(@boundary) + EOL.size
-
-
11
@content_length -= @boundary_size
-
11
true
-
end
-
-
1
def full_boundary
-
11
@boundary + EOL
-
end
-
-
1
def rx
-
50
@rx ||= /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
-
end
-
-
1
def fast_forward_to_first_boundary
-
11
loop do
-
11
read_buffer = @io.gets
-
11
break if read_buffer == full_boundary
-
raise EOFError, "bad content body" if read_buffer.nil?
-
end
-
end
-
-
1
def get_current_head_and_filename_and_content_type_and_name_and_body
-
19
head = nil
-
19
body = ''
-
19
filename = content_type = name = nil
-
19
content = nil
-
-
19
until head && @buf =~ rx
-
42
if !head && i = @buf.index(EOL+EOL)
-
19
head = @buf.slice!(0, i+2) # First \r\n
-
-
19
@buf.slice!(0, 2) # Second \r\n
-
-
19
content_type = head[MULTIPART_CONTENT_TYPE, 1]
-
19
name = head[MULTIPART_CONTENT_DISPOSITION, 1] || head[MULTIPART_CONTENT_ID, 1]
-
-
19
filename = get_filename(head)
-
-
19
if filename
-
9
body = Tempfile.new("RackMultipart")
-
9
body.binmode if body.respond_to?(:binmode)
-
end
-
-
19
next
-
end
-
-
# Save the read body part.
-
23
if head && (@boundary_size+4 < @buf.size)
-
12
body << @buf.slice!(0, @buf.size - (@boundary_size+4))
-
end
-
-
23
content = @io.read(BUFSIZE < @content_length ? BUFSIZE : @content_length)
-
23
raise EOFError, "bad content body" if content.nil? || content.empty?
-
-
23
@buf << content
-
23
@content_length -= content.size
-
end
-
-
19
[head, filename, content_type, name, body]
-
end
-
-
1
def get_filename(head)
-
19
filename = nil
-
19
if head =~ RFC2183
-
filename = Hash[head.scan(DISPPARM)]['filename']
-
filename = $1 if filename and filename =~ /^"(.*)"$/
-
elsif head =~ BROKEN_QUOTED
-
9
filename = $1
-
elsif head =~ BROKEN_UNQUOTED
-
filename = $1
-
end
-
-
19
if filename && filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
-
9
filename = Utils.unescape(filename)
-
end
-
19
if filename && filename !~ /\\[^\\"]/
-
9
filename = filename.gsub(/\\(.)/, '\1')
-
end
-
19
filename
-
end
-
-
1
def get_data(filename, body, content_type, name, head)
-
19
data = nil
-
19
if filename == ""
-
# filename is blank which means no file has been selected
-
1
return data
-
elsif filename
-
8
body.rewind
-
-
# Take the basename of the upload's original filename.
-
# This handles the full Windows paths given by Internet Explorer
-
# (and perhaps other broken user agents) without affecting
-
# those which give the lone filename.
-
8
filename = filename.split(/[\/\\]/).last
-
-
8
data = {:filename => filename, :type => content_type,
-
:name => name, :tempfile => body, :head => head}
-
elsif !filename && content_type && body.is_a?(IO)
-
body.rewind
-
-
# Generic multipart cases, not coming from a form
-
data = {:type => content_type,
-
:name => name, :tempfile => body, :head => head}
-
else
-
10
data = body
-
end
-
-
18
[filename, data]
-
end
-
end
-
end
-
end
-
1
require 'rack/utils'
-
-
1
module Rack
-
# Rack::Request provides a convenient interface to a Rack
-
# environment. It is stateless, the environment +env+ passed to the
-
# constructor will be directly modified.
-
#
-
# req = Rack::Request.new(env)
-
# req.post?
-
# req.params["data"]
-
#
-
# The environment hash passed will store a reference to the Request object
-
# instantiated so that it will only instantiate if an instance of the Request
-
# object doesn't already exist.
-
-
1
class Request
-
# The environment of the request.
-
1
attr_reader :env
-
-
1
def initialize(env)
-
11682
@env = env
-
end
-
-
1
def body; @env["rack.input"] end
-
1591
def script_name; @env["SCRIPT_NAME"].to_s end
-
45836
def path_info; @env["PATH_INFO"].to_s end
-
1
def request_method; @env["REQUEST_METHOD"] end
-
2986
def query_string; @env["QUERY_STRING"].to_s end
-
302
def content_length; @env['CONTENT_LENGTH'] end
-
-
1
def content_type
-
content_type = @env['CONTENT_TYPE']
-
content_type.nil? || content_type.empty? ? nil : content_type
-
end
-
-
4861
def session; @env['rack.session'] ||= {} end
-
39
def session_options; @env['rack.session.options'] ||= {} end
-
1
def logger; @env['rack.logger'] end
-
-
# The media type (type/subtype) portion of the CONTENT_TYPE header
-
# without any media type parameters. e.g., when CONTENT_TYPE is
-
# "text/plain;charset=utf-8", the media-type is "text/plain".
-
#
-
# For more information on the use of media types in HTTP, see:
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
-
1
def media_type
-
content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
-
end
-
-
# The media type parameters provided in CONTENT_TYPE as a Hash, or
-
# an empty Hash if no CONTENT_TYPE or media-type parameters were
-
# provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
-
# this method responds with the following Hash:
-
# { 'charset' => 'utf-8' }
-
1
def media_type_params
-
return {} if content_type.nil?
-
Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
-
collect { |s| s.split('=', 2) }.
-
map { |k,v| [k.downcase, v] }.flatten]
-
end
-
-
# The character set of the request body if a "charset" media type
-
# parameter was given, or nil if no "charset" was specified. Note
-
# that, per RFC2616, text/* media types that specify no explicit
-
# charset are to be considered ISO-8859-1.
-
1
def content_charset
-
media_type_params['charset']
-
end
-
-
1
def scheme
-
2186
if @env['HTTPS'] == 'on'
-
32
'https'
-
2154
elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
-
'https'
-
2154
elsif @env['HTTP_X_FORWARDED_SCHEME']
-
@env['HTTP_X_FORWARDED_SCHEME']
-
2154
elsif @env['HTTP_X_FORWARDED_PROTO']
-
5
@env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
-
else
-
2149
@env["rack.url_scheme"]
-
end
-
end
-
-
1
def ssl?
-
2170
scheme == 'https'
-
end
-
-
1
def host_with_port
-
if forwarded = @env["HTTP_X_FORWARDED_HOST"]
-
forwarded.split(/,\s?/).last
-
else
-
@env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
-
end
-
end
-
-
1
def port
-
if port = host_with_port.split(/:/)[1]
-
port.to_i
-
elsif port = @env['HTTP_X_FORWARDED_PORT']
-
port.to_i
-
elsif ssl?
-
443
-
elsif @env.has_key?("HTTP_X_FORWARDED_HOST")
-
80
-
else
-
@env["SERVER_PORT"].to_i
-
end
-
end
-
-
1
def host
-
# Remove port number.
-
host_with_port.to_s.gsub(/:\d+\z/, '')
-
end
-
-
1
def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
-
1
def path_info=(s); @env["PATH_INFO"] = s.to_s end
-
-
-
# Checks the HTTP request method (or verb) to see if it was of type DELETE
-
1
def delete?; request_method == "DELETE" end
-
-
# Checks the HTTP request method (or verb) to see if it was of type GET
-
1
def get?; request_method == "GET" end
-
-
# Checks the HTTP request method (or verb) to see if it was of type HEAD
-
1
def head?; request_method == "HEAD" end
-
-
# Checks the HTTP request method (or verb) to see if it was of type OPTIONS
-
1
def options?; request_method == "OPTIONS" end
-
-
# Checks the HTTP request method (or verb) to see if it was of type PATCH
-
1
def patch?; request_method == "PATCH" end
-
-
# Checks the HTTP request method (or verb) to see if it was of type POST
-
1
def post?; request_method == "POST" end
-
-
# Checks the HTTP request method (or verb) to see if it was of type PUT
-
1
def put?; request_method == "PUT" end
-
-
# Checks the HTTP request method (or verb) to see if it was of type TRACE
-
1
def trace?; request_method == "TRACE" end
-
-
-
# The set of form-data media-types. Requests that do not indicate
-
# one of the media types presents in this list will not be eligible
-
# for form-data / param parsing.
-
1
FORM_DATA_MEDIA_TYPES = [
-
'application/x-www-form-urlencoded',
-
'multipart/form-data'
-
]
-
-
# The set of media-types. Requests that do not indicate
-
# one of the media types presents in this list will not be eligible
-
# for param parsing like soap attachments or generic multiparts
-
1
PARSEABLE_DATA_MEDIA_TYPES = [
-
'multipart/related',
-
'multipart/mixed'
-
]
-
-
# Determine whether the request body contains form-data by checking
-
# the request Content-Type for one of the media-types:
-
# "application/x-www-form-urlencoded" or "multipart/form-data". The
-
# list of form-data media types can be modified through the
-
# +FORM_DATA_MEDIA_TYPES+ array.
-
#
-
# A request body is also assumed to contain form-data when no
-
# Content-Type header is provided and the request_method is POST.
-
1
def form_data?
-
type = media_type
-
meth = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']
-
(meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
-
end
-
-
# Determine whether the request body contains data by checking
-
# the request media_type against registered parse-data media-types
-
1
def parseable_data?
-
1211
PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
-
end
-
-
# Returns the data received in the query string.
-
1
def GET
-
396
if @env["rack.request.query_string"] == query_string
-
2
@env["rack.request.query_hash"]
-
else
-
394
@env["rack.request.query_string"] = query_string
-
394
@env["rack.request.query_hash"] = parse_query(query_string)
-
end
-
end
-
-
# Returns the data received in the request body.
-
#
-
# This method support both application/x-www-form-urlencoded and
-
# multipart/form-data.
-
1
def POST
-
1542
if @env["rack.input"].nil?
-
raise "Missing rack.input"
-
1542
elsif @env["rack.request.form_input"].eql? @env["rack.input"]
-
1
@env["rack.request.form_hash"]
-
1541
elsif form_data? || parseable_data?
-
330
@env["rack.request.form_input"] = @env["rack.input"]
-
330
unless @env["rack.request.form_hash"] = parse_multipart(env)
-
319
form_vars = @env["rack.input"].read
-
-
# Fix for Safari Ajax postings that always append \0
-
# form_vars.sub!(/\0\z/, '') # performance replacement:
-
319
form_vars.slice!(-1) if form_vars[-1] == ?\0
-
-
319
@env["rack.request.form_vars"] = form_vars
-
319
@env["rack.request.form_hash"] = parse_query(form_vars)
-
-
318
@env["rack.input"].rewind
-
end
-
329
@env["rack.request.form_hash"]
-
else
-
1211
{}
-
end
-
end
-
-
# The union of GET and POST data.
-
1
def params
-
@params ||= self.GET.merge(self.POST)
-
rescue EOFError
-
self.GET
-
end
-
-
# shortcut for request.params[key]
-
1
def [](key)
-
params[key.to_s]
-
end
-
-
# shortcut for request.params[key] = value
-
1
def []=(key, value)
-
params[key.to_s] = value
-
end
-
-
# like Hash#values_at
-
1
def values_at(*keys)
-
keys.map{|key| params[key] }
-
end
-
-
# the referer of the client
-
1
def referer
-
1
@env['HTTP_REFERER']
-
end
-
1
alias referrer referer
-
-
1
def user_agent
-
2
@env['HTTP_USER_AGENT']
-
end
-
-
1
def cookies
-
1299
hash = @env["rack.request.cookie_hash"] ||= {}
-
1299
string = @env["HTTP_COOKIE"]
-
-
1299
return hash if string == @env["rack.request.cookie_string"]
-
92
hash.clear
-
-
# According to RFC 2109:
-
# If multiple cookies satisfy the criteria above, they are ordered in
-
# the Cookie header such that those with more specific Path attributes
-
# precede those with less specific. Ordering with respect to other
-
# attributes (e.g., Domain) is unspecified.
-
147
Utils.parse_query(string, ';,').each { |k,v| hash[k] = Array === v ? v.first : v }
-
92
@env["rack.request.cookie_string"] = string
-
92
hash
-
rescue => error
-
error.message.replace "cannot parse Cookie header: #{error.message}"
-
raise
-
end
-
-
1
def xhr?
-
@env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
-
end
-
-
1
def base_url
-
1
url = scheme + "://"
-
1
url << host
-
-
if scheme == "https" && port != 443 ||
-
1
scheme == "http" && port != 80
-
url << ":#{port}"
-
end
-
-
1
url
-
end
-
-
# Tries to return a remake of the original request URL as a string.
-
1
def url
-
base_url + fullpath
-
end
-
-
1
def path
-
1583
script_name + path_info
-
end
-
-
1
def fullpath
-
1534
query_string.empty? ? path : "#{path}?#{query_string}"
-
end
-
-
1
def accept_encoding
-
@env["HTTP_ACCEPT_ENCODING"].to_s.split(/\s*,\s*/).map do |part|
-
encoding, parameters = part.split(/\s*;\s*/, 2)
-
quality = 1.0
-
if parameters and /\Aq=([\d.]+)/ =~ parameters
-
quality = $1.to_f
-
end
-
[encoding, quality]
-
end
-
end
-
-
1
def trusted_proxy?(ip)
-
694
ip =~ /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|^::1$|^fd[0-9a-f]{2}:.+|^localhost$/i
-
end
-
-
1
def ip
-
2823
remote_addrs = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
-
3517
remote_addrs.reject! { |addr| trusted_proxy?(addr) }
-
-
2823
return remote_addrs.first if remote_addrs.any?
-
-
2820
forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
-
-
2820
if client_ip = @env['HTTP_CLIENT_IP']
-
# If forwarded_ips doesn't include the client_ip, it might be an
-
# ip spoofing attempt, so we ignore HTTP_CLIENT_IP
-
return client_ip if forwarded_ips.include?(client_ip)
-
end
-
-
2820
return forwarded_ips.reject { |ip| trusted_proxy?(ip) }.last || @env["REMOTE_ADDR"]
-
end
-
-
1
protected
-
1
def parse_query(qs)
-
713
Utils.parse_nested_query(qs)
-
end
-
-
1
def parse_multipart(env)
-
330
Rack::Multipart.parse_multipart(env)
-
end
-
end
-
end
-
1
require 'rack/request'
-
1
require 'rack/utils'
-
1
require 'time'
-
-
1
module Rack
-
# Rack::Response provides a convenient interface to create a Rack
-
# response.
-
#
-
# It allows setting of headers and cookies, and provides useful
-
# defaults (a OK response containing HTML).
-
#
-
# You can use Response#write to iteratively generate your response,
-
# but note that this is buffered by Rack::Response until you call
-
# +finish+. +finish+ however can take a block inside which calls to
-
# +write+ are synchronous with the Rack response.
-
#
-
# Your application's +call+ should end returning Response#finish.
-
-
1
class Response
-
1
attr_accessor :length
-
-
1
def initialize(body=[], status=200, header={})
-
809
@status = status.to_i
-
809
@header = Utils::HeaderHash.new("Content-Type" => "text/html").
-
merge(header)
-
-
809
@chunked = "chunked" == @header['Transfer-Encoding']
-
1604
@writer = lambda { |x| @body << x }
-
809
@block = nil
-
809
@length = 0
-
-
809
@body = []
-
-
809
if body.respond_to? :to_str
-
write body.to_str
-
elsif body.respond_to?(:each)
-
809
body.each { |part|
-
795
write part.to_s
-
}
-
else
-
raise TypeError, "stringable or iterable required"
-
end
-
-
809
yield self if block_given?
-
end
-
-
1
attr_reader :header
-
1
attr_accessor :status, :body
-
-
1
def [](key)
-
header[key]
-
end
-
-
1
def []=(key, value)
-
header[key] = value
-
end
-
-
1
def set_cookie(key, value)
-
Utils.set_cookie_header!(header, key, value)
-
end
-
-
1
def delete_cookie(key, value={})
-
Utils.delete_cookie_header!(header, key, value)
-
end
-
-
1
def redirect(target, status=302)
-
self.status = status
-
self["Location"] = target
-
end
-
-
1
def finish(&block)
-
773
@block = block
-
-
773
if [204, 205, 304].include?(status.to_i)
-
header.delete "Content-Type"
-
header.delete "Content-Length"
-
[status.to_i, header, []]
-
else
-
773
[status.to_i, header, self]
-
end
-
end
-
1
alias to_a finish # For *response
-
1
alias to_ary finish # For implicit-splat on Ruby 1.9.2
-
-
1
def each(&callback)
-
@body.each(&callback)
-
@writer = callback
-
@block.call(self) if @block
-
end
-
-
# Append to body and update Content-Length.
-
#
-
# NOTE: Do not mix #write and direct #body access!
-
#
-
1
def write(str)
-
795
s = str.to_s
-
795
@length += Rack::Utils.bytesize(s) unless @chunked
-
795
@writer.call s
-
-
795
header["Content-Length"] = @length.to_s unless @chunked
-
795
str
-
end
-
-
1
def close
-
body.close if body.respond_to?(:close)
-
end
-
-
1
def empty?
-
@block == nil && @body.empty?
-
end
-
-
1
alias headers header
-
-
1
module Helpers
-
1
def invalid?; status < 100 || status >= 600; end
-
-
1
def informational?; status >= 100 && status < 200; end
-
807
def successful?; status >= 200 && status < 300; end
-
665
def redirection?; status >= 300 && status < 400; end
-
602
def client_error?; status >= 400 && status < 500; end
-
1207
def server_error?; status >= 500 && status < 600; end
-
-
1
def ok?; status == 200; end
-
1
def bad_request?; status == 400; end
-
1
def forbidden?; status == 403; end
-
603
def not_found?; status == 404; end
-
1
def method_not_allowed?; status == 405; end
-
1
def unprocessable?; status == 422; end
-
-
1
def redirect?; [301, 302, 303, 307].include? status; end
-
-
# Headers
-
1
attr_reader :headers, :original_headers
-
-
1
def include?(header)
-
!!headers[header]
-
end
-
-
1
def content_type
-
headers["Content-Type"]
-
end
-
-
1
def content_length
-
cl = headers["Content-Length"]
-
cl ? cl.to_i : cl
-
end
-
-
1
def location
-
headers["Location"]
-
end
-
end
-
-
1
include Helpers
-
end
-
end
-
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
-
# bugrep: Andreas Zehnder
-
-
1
require 'time'
-
1
require 'rack/request'
-
1
require 'rack/response'
-
1
begin
-
1
require 'securerandom'
-
rescue LoadError
-
# We just won't get securerandom
-
end
-
-
1
module Rack
-
-
1
module Session
-
-
1
module Abstract
-
1
ENV_SESSION_KEY = 'rack.session'.freeze
-
1
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
-
-
# Thin wrapper around Hash that allows us to lazily load session id into session_options.
-
-
1
class OptionsHash < Hash #:nodoc:
-
1
def initialize(by, env, default_options)
-
@by = by
-
@env = env
-
@session_id_loaded = false
-
merge!(default_options)
-
end
-
-
1
def [](key)
-
load_session_id! if key == :id && session_id_not_loaded?
-
super
-
end
-
-
1
private
-
-
1
def session_id_not_loaded?
-
!(@session_id_loaded || key?(:id))
-
end
-
-
1
def load_session_id!
-
self[:id] = @by.send(:extract_session_id, @env)
-
@session_id_loaded = true
-
end
-
end
-
-
# SessionHash is responsible to lazily load the session from store.
-
-
1
class SessionHash < Hash
-
1
def initialize(by, env)
-
4282
super()
-
4282
@by = by
-
4282
@env = env
-
4282
@loaded = false
-
end
-
-
1
def [](key)
-
2405
load_for_read!
-
2405
super(key.to_s)
-
end
-
-
1
def has_key?(key)
-
load_for_read!
-
super(key.to_s)
-
end
-
1
alias :key? :has_key?
-
1
alias :include? :has_key?
-
-
1
def []=(key, value)
-
1242
load_for_write!
-
1242
super(key.to_s, value)
-
end
-
-
1
def clear
-
1
load_for_write!
-
1
super
-
end
-
-
1
def to_hash
-
load_for_read!
-
h = {}.replace(self)
-
h.delete_if { |k,v| v.nil? }
-
h
-
end
-
-
1
def update(hash)
-
5
load_for_write!
-
5
super(stringify_keys(hash))
-
end
-
-
1
def delete(key)
-
1089
load_for_write!
-
1089
super(key.to_s)
-
end
-
-
1
def inspect
-
6
if loaded?
-
6
super
-
else
-
"#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
-
end
-
end
-
-
1
def exists?
-
return @exists if instance_variable_defined?(:@exists)
-
@exists = @by.send(:session_exists?, @env)
-
end
-
-
1
def loaded?
-
4748
@loaded
-
end
-
-
1
def empty?
-
load_for_read!
-
super
-
end
-
-
1
private
-
-
1
def load_for_read!
-
2405
load! if !loaded? && exists?
-
end
-
-
1
def load_for_write!
-
2337
load! unless loaded?
-
end
-
-
1
def load!
-
id, session = @by.send(:load_session, @env)
-
@env[ENV_SESSION_OPTIONS_KEY][:id] = id
-
replace(stringify_keys(session))
-
@loaded = true
-
end
-
-
1
def stringify_keys(other)
-
5
hash = {}
-
5
other.each do |key, value|
-
6
hash[key.to_s] = value
-
end
-
5
hash
-
end
-
end
-
-
# ID sets up a basic framework for implementing an id based sessioning
-
# service. Cookies sent to the client for maintaining sessions will only
-
# contain an id reference. Only #get_session and #set_session are
-
# required to be overwritten.
-
#
-
# All parameters are optional.
-
# * :key determines the name of the cookie, by default it is
-
# 'rack.session'
-
# * :path, :domain, :expire_after, :secure, and :httponly set the related
-
# cookie options as by Rack::Response#add_cookie
-
# * :skip will not a set a cookie in the response nor update the session state
-
# * :defer will not set a cookie in the response but still update the session
-
# state if it is used with a backend
-
# * :renew (implementation dependent) will prompt the generation of a new
-
# session id, and migration of data to be referenced at the new id. If
-
# :defer is set, it will be overridden and the cookie will be set.
-
# * :sidbits sets the number of bits in length that a generated session
-
# id will be.
-
#
-
# These options can be set on a per request basis, at the location of
-
# env['rack.session.options']. Additionally the id of the session can be
-
# found within the options hash at the key :id. It is highly not
-
# recommended to change its value.
-
#
-
# Is Rack::Utils::Context compatible.
-
#
-
# Not included by default; you must require 'rack/session/abstract/id'
-
# to use.
-
-
1
class ID
-
1
DEFAULT_OPTIONS = {
-
:key => 'rack.session',
-
:path => '/',
-
:domain => nil,
-
:expire_after => nil,
-
:secure => false,
-
:httponly => true,
-
:defer => false,
-
:renew => false,
-
:sidbits => 128,
-
:cookie_only => true,
-
1
:secure_random => (::SecureRandom rescue false)
-
}
-
-
1
attr_reader :key, :default_options
-
-
1
def initialize(app, options={})
-
48
@app = app
-
48
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
-
48
@key = @default_options.delete(:key)
-
48
@cookie_only = @default_options.delete(:cookie_only)
-
48
initialize_sid
-
end
-
-
1
def call(env)
-
86
context(env)
-
end
-
-
1
def context(env, app=@app)
-
86
prepare_session(env)
-
86
status, headers, body = app.call(env)
-
86
commit_session(env, status, headers, body)
-
end
-
-
1
private
-
-
1
def initialize_sid
-
@sidbits = @default_options[:sidbits]
-
@sid_secure = @default_options[:secure_random]
-
@sid_length = @sidbits / 4
-
end
-
-
# Generate a new session id using Ruby #rand. The size of the
-
# session id is controlled by the :sidbits option.
-
# Monkey patch this to use custom methods for session id generation.
-
-
1
def generate_sid(secure = @sid_secure)
-
if secure
-
SecureRandom.hex(@sid_length)
-
else
-
"%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
-
end
-
rescue NotImplementedError
-
generate_sid(false)
-
end
-
-
# Sets the lazy session at 'rack.session' and places options and session
-
# metadata into 'rack.session.options'.
-
-
1
def prepare_session(env)
-
session_was = env[ENV_SESSION_KEY]
-
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
-
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
-
env[ENV_SESSION_KEY].merge! session_was if session_was
-
end
-
-
# Extracts the session id from provided cookies and passes it and the
-
# environment to #get_session.
-
-
1
def load_session(env)
-
33
sid = current_session_id(env)
-
33
sid, session = get_session(env, sid)
-
33
[sid, session || {}]
-
end
-
-
# Extract session id from request object.
-
-
1
def extract_session_id(env)
-
41
request = Rack::Request.new(env)
-
41
sid = request.cookies[@key]
-
41
sid ||= request.params[@key] unless @cookie_only
-
41
sid
-
end
-
-
# Returns the current session id from the OptionsHash.
-
-
1
def current_session_id(env)
-
72
env[ENV_SESSION_OPTIONS_KEY][:id]
-
end
-
-
# Check if the session exists or not.
-
-
1
def session_exists?(env)
-
39
value = current_session_id(env)
-
39
value && !value.empty?
-
end
-
-
# Session should be commited if it was loaded, any of specific options like :renew, :drop
-
# or :expire_after was given and the security permissions match. Skips if skip is given.
-
-
1
def commit_session?(env, session, options)
-
86
if options[:skip]
-
false
-
else
-
86
has_session = loaded_session?(session) || forced_session_update?(session, options)
-
86
has_session && security_matches?(env, options)
-
end
-
end
-
-
1
def loaded_session?(session)
-
!session.is_a?(SessionHash) || session.loaded?
-
end
-
-
1
def forced_session_update?(session, options)
-
26
force_options?(options) && session && !session.empty?
-
end
-
-
1
def force_options?(options)
-
26
options.values_at(:renew, :drop, :defer, :expire_after).any?
-
end
-
-
1
def security_matches?(env, options)
-
62
return true unless options[:secure]
-
2
request = Rack::Request.new(env)
-
2
request.ssl?
-
end
-
-
# Acquires the session from the environment and the session id from
-
# the session options and passes them to #set_session. If successful
-
# and the :defer option is not true, a cookie will be added to the
-
# response with the session's id.
-
-
1
def commit_session(env, status, headers, body)
-
86
session = env['rack.session']
-
86
options = env['rack.session.options']
-
-
86
if options[:drop] || options[:renew]
-
1
session_id = destroy_session(env, options[:id] || generate_sid, options)
-
1
return [status, headers, body] unless session_id
-
end
-
-
86
return [status, headers, body] unless commit_session?(env, session, options)
-
-
61
session.send(:load!) unless loaded_session?(session)
-
61
session = session.to_hash
-
61
session_id ||= options[:id] || generate_sid
-
-
61
if not data = set_session(env, session_id, session, options)
-
env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
-
elsif options[:defer] and not options[:renew]
-
env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
-
else
-
61
cookie = Hash.new
-
61
cookie[:value] = data
-
61
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
-
61
set_cookie(env, headers, cookie.merge!(options))
-
end
-
-
60
[status, headers, body]
-
end
-
-
# Sets the cookie back to the client with session id. We skip the cookie
-
# setting if the value didn't change (sid is the same) or expires was given.
-
-
1
def set_cookie(env, headers, cookie)
-
13
request = Rack::Request.new(env)
-
13
if request.cookies[@key] != cookie[:value] || cookie[:expires]
-
10
Utils.set_cookie_header!(headers, @key, cookie)
-
end
-
end
-
-
# All thread safety and session retrival proceedures should occur here.
-
# Should return [session_id, session].
-
# If nil is provided as the session id, generation of a new valid id
-
# should occur within.
-
-
1
def get_session(env, sid)
-
raise '#get_session not implemented.'
-
end
-
-
# All thread safety and session storage proceedures should occur here.
-
# Should return true or false dependant on whether or not the session
-
# was saved or not.
-
-
1
def set_session(env, sid, session, options)
-
raise '#set_session not implemented.'
-
end
-
-
# All thread safety and session destroy proceedures should occur here.
-
# Should return a new session id or nil if options[:drop]
-
-
1
def destroy_session(env, sid, options)
-
raise '#destroy_session not implemented'
-
end
-
end
-
end
-
end
-
end
-
1
require 'openssl'
-
1
require 'rack/request'
-
1
require 'rack/response'
-
1
require 'rack/session/abstract/id'
-
-
1
module Rack
-
-
1
module Session
-
-
# Rack::Session::Cookie provides simple cookie based session management.
-
# By default, the session is a Ruby Hash stored as base64 encoded marshalled
-
# data set to :key (default: rack.session). The object that encodes the
-
# session data is configurable and must respond to +encode+ and +decode+.
-
# Both methods must take a string and return a string.
-
#
-
# When the secret key is set, cookie data is checked for data integrity.
-
# The old secret key is also accepted and allows graceful secret rotation.
-
#
-
# Example:
-
#
-
# use Rack::Session::Cookie, :key => 'rack.session',
-
# :domain => 'foo.com',
-
# :path => '/',
-
# :expire_after => 2592000,
-
# :secret => 'change_me',
-
# :old_secret => 'also_change_me'
-
#
-
# All parameters are optional.
-
#
-
# Example of a cookie with no encoding:
-
#
-
# Rack::Session::Cookie.new(application, {
-
# :coder => Rack::Session::Cookie::Identity.new
-
# })
-
#
-
# Example of a cookie with custom encoding:
-
#
-
# Rack::Session::Cookie.new(application, {
-
# :coder => Class.new {
-
# def encode(str); str.reverse; end
-
# def decode(str); str.reverse; end
-
# }.new
-
# })
-
#
-
-
1
class Cookie < Abstract::ID
-
# Encode session cookies as Base64
-
1
class Base64
-
1
def encode(str)
-
[str].pack('m')
-
end
-
-
1
def decode(str)
-
str.unpack('m').first
-
end
-
-
# Encode session cookies as Marshaled Base64 data
-
1
class Marshal < Base64
-
1
def encode(str)
-
super(::Marshal.dump(str))
-
end
-
-
1
def decode(str)
-
::Marshal.load(super(str)) rescue nil
-
end
-
end
-
end
-
-
# Use no encoding for session cookies
-
1
class Identity
-
1
def encode(str); str; end
-
1
def decode(str); str; end
-
end
-
-
# Reverse string encoding. (trollface)
-
1
class Reverse
-
1
def encode(str); str.reverse; end
-
1
def decode(str); str.reverse; end
-
end
-
-
1
attr_reader :coder
-
-
1
def initialize(app, options={})
-
28
@secrets = options.values_at(:secret, :old_secret).compact
-
28
@coder = options[:coder] ||= Base64::Marshal.new
-
28
super(app, options.merge!(:cookie_only => true))
-
end
-
-
1
private
-
-
1
def load_session(env)
-
36
data = unpacked_cookie_data(env)
-
36
data = persistent_session_id!(data)
-
36
[data["session_id"], data]
-
end
-
-
1
def extract_session_id(env)
-
21
unpacked_cookie_data(env)["session_id"]
-
end
-
-
1
def unpacked_cookie_data(env)
-
env["rack.session.unpacked_cookie_data"] ||= begin
-
request = Rack::Request.new(env)
-
session_data = request.cookies[@key]
-
-
if @secrets.size > 0 && session_data
-
session_data, digest = session_data.split("--")
-
-
if session_data && digest
-
ok = @secrets.any? do |secret|
-
secret && digest == generate_hmac(session_data, secret)
-
end
-
end
-
-
session_data = nil unless ok
-
end
-
-
coder.decode(session_data) || {}
-
end
-
end
-
-
1
def persistent_session_id!(data, sid=nil)
-
36
data ||= {}
-
36
data["session_id"] ||= sid || generate_sid
-
36
data
-
end
-
-
# Overwrite set cookie to bypass content equality and always stream the cookie.
-
-
1
def set_cookie(env, headers, cookie)
-
Utils.set_cookie_header!(headers, @key, cookie)
-
end
-
-
1
def set_session(env, session_id, session, options)
-
session = session.merge("session_id" => session_id)
-
session_data = coder.encode(session)
-
-
if @secrets.first
-
session_data = "#{session_data}--#{generate_hmac(session_data, @secrets.first)}"
-
end
-
-
if session_data.size > (4096 - @key.size)
-
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
-
nil
-
else
-
session_data
-
end
-
end
-
-
1
def destroy_session(env, session_id, options)
-
# Nothing to do here, data is in the client
-
3
generate_sid unless options[:drop]
-
end
-
-
1
def generate_hmac(data, secret)
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
-
end
-
-
end
-
end
-
end
-
# -*- encoding: binary -*-
-
1
require 'fileutils'
-
1
require 'set'
-
1
require 'tempfile'
-
1
require 'rack/multipart'
-
-
4
major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
-
-
1
if major == 1 && minor < 9
-
require 'rack/backports/uri/common_18'
-
elsif major == 1 && minor == 9 && patch < 3
-
require 'rack/backports/uri/common_192'
-
else
-
1
require 'uri/common'
-
end
-
-
1
module Rack
-
# Rack::Utils contains a grab-bag of useful methods for writing web
-
# applications adopted from all kinds of Ruby libraries.
-
-
1
module Utils
-
# URI escapes. (CGI style space to +)
-
1
def escape(s)
-
400
URI.encode_www_form_component(s)
-
end
-
1
module_function :escape
-
-
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
-
# true URI escaping.
-
1
def escape_path(s)
-
4
escape(s).gsub('+', '%20')
-
end
-
1
module_function :escape_path
-
-
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
-
# target encoding of the string returned, and it defaults to UTF-8
-
1
if defined?(::Encoding)
-
1
def unescape(s, encoding = Encoding::UTF_8)
-
851
URI.decode_www_form_component(s, encoding)
-
end
-
else
-
def unescape(s, encoding = nil)
-
URI.decode_www_form_component(s, encoding)
-
end
-
end
-
1
module_function :unescape
-
-
1
DEFAULT_SEP = /[&;] */n
-
-
1
class << self
-
1
attr_accessor :key_space_limit
-
end
-
-
# The default number of bytes to allow parameter keys to take up.
-
# This helps prevent a rogue client from flooding a Request.
-
1
self.key_space_limit = 65536
-
-
# Stolen from Mongrel, with some small modifications:
-
# Parses a query string by breaking it up at the '&'
-
# and ';' characters. You can also use this to parse
-
# cookies by changing the characters used in the second
-
# parameter (which defaults to '&;').
-
1
def parse_query(qs, d = nil)
-
248
params = KeySpaceConstrainedParams.new
-
-
248
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
-
761
k, v = p.split('=', 2).map { |x| unescape(x) }
-
-
279
if cur = params[k]
-
if cur.class == Array
-
params[k] << v
-
else
-
params[k] = [cur, v]
-
end
-
else
-
279
params[k] = v
-
end
-
end
-
-
248
return params.to_params_hash
-
end
-
1
module_function :parse_query
-
-
1
def parse_nested_query(qs, d = nil)
-
832
params = KeySpaceConstrainedParams.new
-
-
832
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
-
418
k, v = p.split('=', 2).map { |s| unescape(s) }
-
-
147
normalize_params(params, k, v)
-
end
-
-
828
return params.to_params_hash
-
end
-
1
module_function :parse_nested_query
-
-
1
def normalize_params(params, name, v = nil)
-
282
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
-
282
k = $1 || ''
-
282
after = $' || ''
-
-
282
return if k.empty?
-
-
276
if after == ""
-
117
params[k] = v
-
elsif after == "[]"
-
38
params[k] ||= []
-
38
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
-
38
params[k] << v
-
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
-
33
child_key = $1
-
33
params[k] ||= []
-
33
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
-
31
if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
-
10
normalize_params(params[k].last, child_key, v)
-
else
-
21
params[k] << normalize_params(params.class.new, child_key, v)
-
end
-
else
-
88
params[k] ||= params.class.new
-
88
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
-
86
params[k] = normalize_params(params[k], after, v)
-
end
-
-
270
return params
-
end
-
1
module_function :normalize_params
-
-
1
def params_hash_type?(obj)
-
119
obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
-
end
-
1
module_function :params_hash_type?
-
-
1
def build_query(params)
-
params.map { |k, v|
-
if v.class == Array
-
build_query(v.map { |x| [k, x] })
-
else
-
v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
-
end
-
}.join("&")
-
end
-
1
module_function :build_query
-
-
1
def build_nested_query(value, prefix = nil)
-
case value
-
when Array
-
value.map { |v|
-
build_nested_query(v, "#{prefix}[]")
-
}.join("&")
-
when Hash
-
value.map { |k, v|
-
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
-
}.join("&")
-
when String
-
raise ArgumentError, "value must be a Hash" if prefix.nil?
-
"#{prefix}=#{escape(value)}"
-
else
-
prefix
-
end
-
end
-
1
module_function :build_nested_query
-
-
1
ESCAPE_HTML = {
-
"&" => "&",
-
"<" => "<",
-
">" => ">",
-
"'" => "'",
-
'"' => """,
-
"/" => "/"
-
}
-
1
if //.respond_to?(:encoding)
-
1
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
-
else
-
# On 1.8, there is a kcode = 'u' bug that allows for XSS otherwhise
-
# TODO doesn't apply to jruby, so a better condition above might be preferable?
-
ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
-
end
-
-
# Escape ampersands, brackets and quotes to their HTML/XML entities.
-
1
def escape_html(string)
-
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
-
end
-
1
module_function :escape_html
-
-
1
def select_best_encoding(available_encodings, accept_encoding)
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
-
-
expanded_accept_encoding =
-
accept_encoding.map { |m, q|
-
if m == "*"
-
(available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
-
else
-
[[m, q]]
-
end
-
}.inject([]) { |mem, list|
-
mem + list
-
}
-
-
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
-
-
unless encoding_candidates.include?("identity")
-
encoding_candidates.push("identity")
-
end
-
-
expanded_accept_encoding.find_all { |m, q|
-
q == 0.0
-
}.each { |m, _|
-
encoding_candidates.delete(m)
-
}
-
-
return (encoding_candidates & available_encodings)[0]
-
end
-
1
module_function :select_best_encoding
-
-
1
def set_cookie_header!(header, key, value)
-
99
case value
-
when Hash
-
99
domain = "; domain=" + value[:domain] if value[:domain]
-
99
path = "; path=" + value[:path] if value[:path]
-
# According to RFC 2109, we need dashes here.
-
# N.B.: cgi.rb uses spaces...
-
expires = "; expires=" +
-
99
rfc2822(value[:expires].clone.gmtime) if value[:expires]
-
99
secure = "; secure" if value[:secure]
-
99
httponly = "; HttpOnly" if value[:httponly]
-
99
value = value[:value]
-
end
-
99
value = [value] unless Array === value
-
99
cookie = escape(key) + "=" +
-
99
value.map { |v| escape v }.join("&") +
-
"#{domain}#{path}#{expires}#{secure}#{httponly}"
-
-
99
case header["Set-Cookie"]
-
when nil, ''
-
95
header["Set-Cookie"] = cookie
-
when String
-
4
header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
-
when Array
-
header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
-
end
-
-
nil
-
end
-
1
module_function :set_cookie_header!
-
-
1
def delete_cookie_header!(header, key, value = {})
-
8
case header["Set-Cookie"]
-
when nil, ''
-
7
cookies = []
-
when String
-
1
cookies = header["Set-Cookie"].split("\n")
-
when Array
-
cookies = header["Set-Cookie"]
-
end
-
-
8
cookies.reject! { |cookie|
-
2
if value[:domain]
-
cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
-
elsif value[:path]
-
cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
-
else
-
2
cookie =~ /\A#{escape(key)}=/
-
end
-
}
-
-
8
header["Set-Cookie"] = cookies.join("\n")
-
-
8
set_cookie_header!(header, key,
-
{:value => '', :path => nil, :domain => nil,
-
:expires => Time.at(0) }.merge(value))
-
-
nil
-
end
-
1
module_function :delete_cookie_header!
-
-
# Return the bytesize of String; uses String#size under Ruby 1.8 and
-
# String#bytesize under 1.9.
-
1
if ''.respond_to?(:bytesize)
-
1
def bytesize(string)
-
803
string.bytesize
-
end
-
else
-
def bytesize(string)
-
string.size
-
end
-
end
-
1
module_function :bytesize
-
-
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
-
# of '% %b %Y'.
-
# It assumes that the time is in GMT to comply to the RFC 2109.
-
#
-
# NOTE: I'm not sure the RFC says it requires GMT, but is ambigous enough
-
# that I'm certain someone implemented only that option.
-
# Do not use %a and %b from Time.strptime, it would use localized names for
-
# weekday and month.
-
#
-
1
def rfc2822(time)
-
18
wday = Time::RFC2822_DAY_NAME[time.wday]
-
18
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
-
18
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
-
end
-
1
module_function :rfc2822
-
-
# Parses the "Range:" header, if present, into an array of Range objects.
-
# Returns nil if the header is missing or syntactically invalid.
-
# Returns an empty array if none of the ranges are satisfiable.
-
1
def byte_ranges(env, size)
-
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
-
32
http_range = env['HTTP_RANGE']
-
32
return nil unless http_range
-
ranges = []
-
http_range.split(/,\s*/).each do |range_spec|
-
matches = range_spec.match(/bytes=(\d*)-(\d*)/)
-
return nil unless matches
-
r0,r1 = matches[1], matches[2]
-
if r0.empty?
-
return nil if r1.empty?
-
# suffix-byte-range-spec, represents trailing suffix of file
-
r0 = [size - r1.to_i, 0].max
-
r1 = size - 1
-
else
-
r0 = r0.to_i
-
if r1.empty?
-
r1 = size - 1
-
else
-
r1 = r1.to_i
-
return nil if r1 < r0 # backwards range is syntactically invalid
-
r1 = size-1 if r1 >= size
-
end
-
end
-
ranges << (r0..r1) if r0 <= r1
-
end
-
ranges
-
end
-
1
module_function :byte_ranges
-
-
# Context allows the use of a compatible middleware at different points
-
# in a request handling stack. A compatible middleware must define
-
# #context which should take the arguments env and app. The first of which
-
# would be the request environment. The second of which would be the rack
-
# application that the request would be forwarded to.
-
1
class Context
-
1
attr_reader :for, :app
-
-
1
def initialize(app_f, app_r)
-
raise 'running context does not respond to #context' unless app_f.respond_to? :context
-
@for, @app = app_f, app_r
-
end
-
-
1
def call(env)
-
@for.context(env, @app)
-
end
-
-
1
def recontext(app)
-
self.class.new(@for, app)
-
end
-
-
1
def context(env, app=@app)
-
recontext(app).call(env)
-
end
-
end
-
-
# A case-insensitive Hash that preserves the original case of a
-
# header when set.
-
1
class HeaderHash < Hash
-
1
def self.new(hash={})
-
809
HeaderHash === hash ? hash : super(hash)
-
end
-
-
1
def initialize(hash={})
-
809
super()
-
809
@names = {}
-
1618
hash.each { |k, v| self[k] = v }
-
end
-
-
1
def each
-
super do |k, v|
-
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
-
end
-
end
-
-
1
def to_hash
-
hash = {}
-
each { |k,v| hash[k] = v }
-
hash
-
end
-
-
1
def [](k)
-
4020
super(k) || super(@names[k.downcase])
-
end
-
-
1
def []=(k, v)
-
2678
canonical = k.downcase
-
2678
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
-
2678
@names[k] = @names[canonical] = k
-
2678
super k, v
-
end
-
-
1
def delete(k)
-
canonical = k.downcase
-
result = super @names.delete(canonical)
-
@names.delete_if { |name,| name.downcase == canonical }
-
result
-
end
-
-
1
def include?(k)
-
@names.include?(k) || @names.include?(k.downcase)
-
end
-
-
1
alias_method :has_key?, :include?
-
1
alias_method :member?, :include?
-
1
alias_method :key?, :include?
-
-
1
def merge!(other)
-
1901
other.each { |k, v| self[k] = v }
-
809
self
-
end
-
-
1
def merge(other)
-
809
hash = dup
-
809
hash.merge! other
-
end
-
-
1
def replace(other)
-
clear
-
other.each { |k, v| self[k] = v }
-
self
-
end
-
end
-
-
1
class KeySpaceConstrainedParams
-
1
def initialize(limit = Utils.key_space_limit)
-
1165
@limit = limit
-
1165
@size = 0
-
1165
@params = {}
-
end
-
-
1
def [](key)
-
802
@params[key]
-
end
-
-
1
def []=(key, value)
-
572
@size += key.size unless @params.key?(key)
-
572
raise RangeError, 'exceeded available parameter key space' if @size > @limit
-
572
@params[key] = value
-
end
-
-
1
def key?(key)
-
15
@params.key?(key)
-
end
-
-
1
def to_params_hash
-
1159
hash = @params
-
1159
hash.keys.each do |key|
-
482
value = hash[key]
-
482
if value.kind_of?(self.class)
-
51
hash[key] = value.to_params_hash
-
elsif value.kind_of?(Array)
-
94
value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
-
end
-
end
-
1159
hash
-
end
-
end
-
-
# Every standard HTTP code mapped to the appropriate message.
-
# Generated with:
-
# curl -s http://www.iana.org/assignments/http-status-codes | \
-
# ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and
-
# puts " #{m[1]} => \x27#{m[2].strip}x27,"'
-
1
HTTP_STATUS_CODES = {
-
100 => 'Continue',
-
101 => 'Switching Protocols',
-
102 => 'Processing',
-
200 => 'OK',
-
201 => 'Created',
-
202 => 'Accepted',
-
203 => 'Non-Authoritative Information',
-
204 => 'No Content',
-
205 => 'Reset Content',
-
206 => 'Partial Content',
-
207 => 'Multi-Status',
-
226 => 'IM Used',
-
300 => 'Multiple Choices',
-
301 => 'Moved Permanently',
-
302 => 'Found',
-
303 => 'See Other',
-
304 => 'Not Modified',
-
305 => 'Use Proxy',
-
306 => 'Reserved',
-
307 => 'Temporary Redirect',
-
400 => 'Bad Request',
-
401 => 'Unauthorized',
-
402 => 'Payment Required',
-
403 => 'Forbidden',
-
404 => 'Not Found',
-
405 => 'Method Not Allowed',
-
406 => 'Not Acceptable',
-
407 => 'Proxy Authentication Required',
-
408 => 'Request Timeout',
-
409 => 'Conflict',
-
410 => 'Gone',
-
411 => 'Length Required',
-
412 => 'Precondition Failed',
-
413 => 'Request Entity Too Large',
-
414 => 'Request-URI Too Long',
-
415 => 'Unsupported Media Type',
-
416 => 'Requested Range Not Satisfiable',
-
417 => 'Expectation Failed',
-
418 => "I'm a Teapot",
-
422 => 'Unprocessable Entity',
-
423 => 'Locked',
-
424 => 'Failed Dependency',
-
426 => 'Upgrade Required',
-
500 => 'Internal Server Error',
-
501 => 'Not Implemented',
-
502 => 'Bad Gateway',
-
503 => 'Service Unavailable',
-
504 => 'Gateway Timeout',
-
505 => 'HTTP Version Not Supported',
-
506 => 'Variant Also Negotiates',
-
507 => 'Insufficient Storage',
-
510 => 'Not Extended',
-
}
-
-
# Responses with HTTP status codes that should not have an entity body
-
1
STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
-
-
1
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
-
52
[message.downcase.gsub(/\s|-/, '_').to_sym, code]
-
}.flatten]
-
-
1
def status_code(status)
-
9033
if status.is_a?(Symbol)
-
366
SYMBOL_TO_STATUS_CODE[status] || 500
-
else
-
8667
status.to_i
-
end
-
end
-
1
module_function :status_code
-
-
1
Multipart = Rack::Multipart
-
-
end
-
end
-
1
require 'rack'
-
-
# = HTTP Caching For Rack
-
#
-
# Rack::Cache is suitable as a quick, drop-in component to enable HTTP caching
-
# for Rack-enabled applications that produce freshness (+Expires+, +Cache-Control+)
-
# and/or validation (+Last-Modified+, +ETag+) information.
-
#
-
# * Standards-based (RFC 2616 compliance)
-
# * Freshness/expiration based caching and validation
-
# * Supports HTTP Vary
-
# * Portable: 100% Ruby / works with any Rack-enabled framework
-
# * Disk, memcached, and heap memory storage backends
-
#
-
# === Usage
-
#
-
# Create with default options:
-
# require 'rack/cache'
-
# Rack::Cache.new(app, :verbose => true, :entitystore => 'file:cache')
-
#
-
# Within a rackup file (or with Rack::Builder):
-
# require 'rack/cache'
-
# use Rack::Cache do
-
# set :verbose, true
-
# set :metastore, 'memcached://localhost:11211/meta'
-
# set :entitystore, 'file:/var/cache/rack'
-
# end
-
# run app
-
1
module Rack::Cache
-
1
autoload :Request, 'rack/cache/request'
-
1
autoload :Response, 'rack/cache/response'
-
1
autoload :Context, 'rack/cache/context'
-
1
autoload :Storage, 'rack/cache/storage'
-
1
autoload :CacheControl, 'rack/cache/cachecontrol'
-
-
# Create a new Rack::Cache middleware component that fetches resources from
-
# the specified backend application. The +options+ Hash can be used to
-
# specify default configuration values (see attributes defined in
-
# Rack::Cache::Options for possible key/values). When a block is given, it
-
# is executed within the context of the newly create Rack::Cache::Context
-
# object.
-
1
def self.new(backend, options={}, &b)
-
Context.new(backend, options, &b)
-
end
-
end
-
1
module Rack
-
1
module Cache
-
-
# Parses a Cache-Control header and exposes the directives as a Hash.
-
# Directives that do not have values are set to +true+.
-
1
class CacheControl < Hash
-
1
def initialize(value=nil)
-
parse(value)
-
end
-
-
# Indicates that the response MAY be cached by any cache, even if it
-
# would normally be non-cacheable or cacheable only within a non-
-
# shared cache.
-
#
-
# A response may be considered public without this directive if the
-
# private directive is not set and the request does not include an
-
# Authorization header.
-
1
def public?
-
self['public']
-
end
-
-
# Indicates that all or part of the response message is intended for
-
# a single user and MUST NOT be cached by a shared cache. This
-
# allows an origin server to state that the specified parts of the
-
# response are intended for only one user and are not a valid
-
# response for requests by other users. A private (non-shared) cache
-
# MAY cache the response.
-
#
-
# Note: This usage of the word private only controls where the
-
# response may be cached, and cannot ensure the privacy of the
-
# message content.
-
1
def private?
-
self['private']
-
end
-
-
# When set in a response, a cache MUST NOT use the response to satisfy a
-
# subsequent request without successful revalidation with the origin
-
# server. This allows an origin server to prevent caching even by caches
-
# that have been configured to return stale responses to client requests.
-
#
-
# Note that this does not necessary imply that the response may not be
-
# stored by the cache, only that the cache cannot serve it without first
-
# making a conditional GET request with the origin server.
-
#
-
# When set in a request, the server MUST NOT use a cached copy for its
-
# response. This has quite different semantics compared to the no-cache
-
# directive on responses. When the client specifies no-cache, it causes
-
# an end-to-end reload, forcing each cache to update their cached copies.
-
1
def no_cache?
-
self['no-cache']
-
end
-
-
# Indicates that the response MUST NOT be stored under any circumstances.
-
#
-
# The purpose of the no-store directive is to prevent the
-
# inadvertent release or retention of sensitive information (for
-
# example, on backup tapes). The no-store directive applies to the
-
# entire message, and MAY be sent either in a response or in a
-
# request. If sent in a request, a cache MUST NOT store any part of
-
# either this request or any response to it. If sent in a response,
-
# a cache MUST NOT store any part of either this response or the
-
# request that elicited it. This directive applies to both non-
-
# shared and shared caches. "MUST NOT store" in this context means
-
# that the cache MUST NOT intentionally store the information in
-
# non-volatile storage, and MUST make a best-effort attempt to
-
# remove the information from volatile storage as promptly as
-
# possible after forwarding it.
-
#
-
# The purpose of this directive is to meet the stated requirements
-
# of certain users and service authors who are concerned about
-
# accidental releases of information via unanticipated accesses to
-
# cache data structures. While the use of this directive might
-
# improve privacy in some cases, we caution that it is NOT in any
-
# way a reliable or sufficient mechanism for ensuring privacy. In
-
# particular, malicious or compromised caches might not recognize or
-
# obey this directive, and communications networks might be
-
# vulnerable to eavesdropping.
-
1
def no_store?
-
self['no-store']
-
end
-
-
# The expiration time of an entity MAY be specified by the origin
-
# server using the Expires header (see section 14.21). Alternatively,
-
# it MAY be specified using the max-age directive in a response. When
-
# the max-age cache-control directive is present in a cached response,
-
# the response is stale if its current age is greater than the age
-
# value given (in seconds) at the time of a new request for that
-
# resource. The max-age directive on a response implies that the
-
# response is cacheable (i.e., "public") unless some other, more
-
# restrictive cache directive is also present.
-
#
-
# If a response includes both an Expires header and a max-age
-
# directive, the max-age directive overrides the Expires header, even
-
# if the Expires header is more restrictive. This rule allows an origin
-
# server to provide, for a given response, a longer expiration time to
-
# an HTTP/1.1 (or later) cache than to an HTTP/1.0 cache. This might be
-
# useful if certain HTTP/1.0 caches improperly calculate ages or
-
# expiration times, perhaps due to desynchronized clocks.
-
#
-
# Many HTTP/1.0 cache implementations will treat an Expires value that
-
# is less than or equal to the response Date value as being equivalent
-
# to the Cache-Control response directive "no-cache". If an HTTP/1.1
-
# cache receives such a response, and the response does not include a
-
# Cache-Control header field, it SHOULD consider the response to be
-
# non-cacheable in order to retain compatibility with HTTP/1.0 servers.
-
#
-
# When the max-age directive is included in the request, it indicates
-
# that the client is willing to accept a response whose age is no
-
# greater than the specified time in seconds.
-
1
def max_age
-
self['max-age'].to_i if key?('max-age')
-
end
-
-
# If a response includes an s-maxage directive, then for a shared
-
# cache (but not for a private cache), the maximum age specified by
-
# this directive overrides the maximum age specified by either the
-
# max-age directive or the Expires header. The s-maxage directive
-
# also implies the semantics of the proxy-revalidate directive. i.e.,
-
# that the shared cache must not use the entry after it becomes stale
-
# to respond to a subsequent request without first revalidating it with
-
# the origin server. The s-maxage directive is always ignored by a
-
# private cache.
-
1
def shared_max_age
-
self['s-maxage'].to_i if key?('s-maxage')
-
end
-
1
alias_method :s_maxage, :shared_max_age
-
-
# Because a cache MAY be configured to ignore a server's specified
-
# expiration time, and because a client request MAY include a max-
-
# stale directive (which has a similar effect), the protocol also
-
# includes a mechanism for the origin server to require revalidation
-
# of a cache entry on any subsequent use. When the must-revalidate
-
# directive is present in a response received by a cache, that cache
-
# MUST NOT use the entry after it becomes stale to respond to a
-
# subsequent request without first revalidating it with the origin
-
# server. (I.e., the cache MUST do an end-to-end revalidation every
-
# time, if, based solely on the origin server's Expires or max-age
-
# value, the cached response is stale.)
-
#
-
# The must-revalidate directive is necessary to support reliable
-
# operation for certain protocol features. In all circumstances an
-
# HTTP/1.1 cache MUST obey the must-revalidate directive; in
-
# particular, if the cache cannot reach the origin server for any
-
# reason, it MUST generate a 504 (Gateway Timeout) response.
-
#
-
# Servers SHOULD send the must-revalidate directive if and only if
-
# failure to revalidate a request on the entity could result in
-
# incorrect operation, such as a silently unexecuted financial
-
# transaction. Recipients MUST NOT take any automated action that
-
# violates this directive, and MUST NOT automatically provide an
-
# unvalidated copy of the entity if revalidation fails.
-
1
def must_revalidate?
-
self['must-revalidate']
-
end
-
-
# The proxy-revalidate directive has the same meaning as the must-
-
# revalidate directive, except that it does not apply to non-shared
-
# user agent caches. It can be used on a response to an
-
# authenticated request to permit the user's cache to store and
-
# later return the response without needing to revalidate it (since
-
# it has already been authenticated once by that user), while still
-
# requiring proxies that service many users to revalidate each time
-
# (in order to make sure that each user has been authenticated).
-
# Note that such authenticated responses also need the public cache
-
# control directive in order to allow them to be cached at all.
-
1
def proxy_revalidate?
-
self['proxy-revalidate']
-
end
-
-
1
def to_s
-
bools, vals = [], []
-
each do |key,value|
-
if value == true
-
bools << key
-
elsif value
-
vals << "#{key}=#{value}"
-
end
-
end
-
(bools.sort + vals.sort).join(', ')
-
end
-
-
1
private
-
1
def parse(value)
-
return if value.nil? || value.empty?
-
value.delete(' ').split(',').each do |part|
-
next if part.empty?
-
name, value = part.split('=', 2)
-
self[name.downcase] = (value || true) unless name.empty?
-
end
-
self
-
end
-
end
-
end
-
end
-
1
require 'rack/cache/options'
-
1
require 'rack/cache/request'
-
1
require 'rack/cache/response'
-
1
require 'rack/cache/storage'
-
-
1
module Rack::Cache
-
# Implements Rack's middleware interface and provides the context for all
-
# cache logic, including the core logic engine.
-
1
class Context
-
1
include Rack::Cache::Options
-
-
# Array of trace Symbols
-
1
attr_reader :trace
-
-
# The Rack application object immediately downstream.
-
1
attr_reader :backend
-
-
1
def initialize(backend, options={})
-
@backend = backend
-
@trace = []
-
@env = nil
-
-
initialize_options options
-
yield self if block_given?
-
-
@private_header_keys =
-
private_headers.map { |name| "HTTP_#{name.upcase.tr('-', '_')}" }
-
end
-
-
# The configured MetaStore instance. Changing the rack-cache.metastore
-
# value effects the result of this method immediately.
-
1
def metastore
-
uri = options['rack-cache.metastore']
-
storage.resolve_metastore_uri(uri)
-
end
-
-
# The configured EntityStore instance. Changing the rack-cache.entitystore
-
# value effects the result of this method immediately.
-
1
def entitystore
-
uri = options['rack-cache.entitystore']
-
storage.resolve_entitystore_uri(uri)
-
end
-
-
# The Rack call interface. The receiver acts as a prototype and runs
-
# each request in a dup object unless the +rack.run_once+ variable is
-
# set in the environment.
-
1
def call(env)
-
if env['rack.run_once']
-
call! env
-
else
-
clone.call! env
-
end
-
end
-
-
# The real Rack call interface. The caching logic is performed within
-
# the context of the receiver.
-
1
def call!(env)
-
@trace = []
-
@default_options.each { |k,v| env[k] ||= v }
-
@env = env
-
@request = Request.new(@env.dup.freeze)
-
-
response =
-
if @request.get? || @request.head?
-
if !@env['HTTP_EXPECT'] && !@env['rack-cache.force-pass']
-
lookup
-
else
-
pass
-
end
-
else
-
invalidate
-
end
-
-
# log trace and set X-Rack-Cache tracing header
-
trace = @trace.join(', ')
-
response.headers['X-Rack-Cache'] = trace
-
-
# write log message to rack.errors
-
if verbose?
-
message = "cache: [%s %s] %s\n" %
-
[@request.request_method, @request.fullpath, trace]
-
@env['rack.errors'].write(message)
-
end
-
-
# tidy up response a bit
-
if (@request.get? || @request.head?) && not_modified?(response)
-
response.not_modified!
-
end
-
-
if @request.head?
-
response.body.close if response.body.respond_to?(:close)
-
response.body = []
-
end
-
response.to_a
-
end
-
-
1
private
-
-
# Record that an event took place.
-
1
def record(event)
-
@trace << event
-
end
-
-
# Does the request include authorization or other sensitive information
-
# that should cause the response to be considered private by default?
-
# Private responses are not stored in the cache.
-
1
def private_request?
-
@private_header_keys.any? { |key| @env.key?(key) }
-
end
-
-
# Determine if the #response validators (ETag, Last-Modified) matches
-
# a conditional value specified in #request.
-
1
def not_modified?(response)
-
last_modified = @request.env['HTTP_IF_MODIFIED_SINCE']
-
if etags = @request.env['HTTP_IF_NONE_MATCH']
-
etags = etags.split(/\s*,\s*/)
-
(etags.include?(response.etag) || etags.include?('*')) && (!last_modified || response.last_modified == last_modified)
-
elsif last_modified
-
response.last_modified == last_modified
-
end
-
end
-
-
# Whether the cache entry is "fresh enough" to satisfy the request.
-
1
def fresh_enough?(entry)
-
if entry.fresh?
-
if allow_revalidate? && max_age = @request.cache_control.max_age
-
max_age > 0 && max_age >= entry.age
-
else
-
true
-
end
-
end
-
end
-
-
# Delegate the request to the backend and create the response.
-
1
def forward
-
Response.new(*backend.call(@env))
-
end
-
-
# The request is sent to the backend, and the backend's response is sent
-
# to the client, but is not entered into the cache.
-
1
def pass
-
record :pass
-
forward
-
end
-
-
# Invalidate POST, PUT, DELETE and all methods not understood by this cache
-
# See RFC2616 13.10
-
1
def invalidate
-
metastore.invalidate(@request, entitystore)
-
rescue Exception => e
-
log_error(e)
-
pass
-
else
-
record :invalidate
-
pass
-
end
-
-
# Try to serve the response from cache. When a matching cache entry is
-
# found and is fresh, use it as the response without forwarding any
-
# request to the backend. When a matching cache entry is found but is
-
# stale, attempt to #validate the entry with the backend using conditional
-
# GET. When no matching cache entry is found, trigger #miss processing.
-
1
def lookup
-
if @request.no_cache? && allow_reload?
-
record :reload
-
fetch
-
else
-
begin
-
entry = metastore.lookup(@request, entitystore)
-
rescue Exception => e
-
log_error(e)
-
return pass
-
end
-
if entry
-
if fresh_enough?(entry)
-
record :fresh
-
entry.headers['Age'] = entry.age.to_s
-
entry
-
else
-
record :stale
-
validate(entry)
-
end
-
else
-
record :miss
-
fetch
-
end
-
end
-
end
-
-
# Validate that the cache entry is fresh. The original request is used
-
# as a template for a conditional GET request with the backend.
-
1
def validate(entry)
-
# send no head requests because we want content
-
@env['REQUEST_METHOD'] = 'GET'
-
-
# add our cached last-modified validator to the environment
-
@env['HTTP_IF_MODIFIED_SINCE'] = entry.last_modified
-
-
# Add our cached etag validator to the environment.
-
# We keep the etags from the client to handle the case when the client
-
# has a different private valid entry which is not cached here.
-
cached_etags = entry.etag.to_s.split(/\s*,\s*/)
-
request_etags = @request.env['HTTP_IF_NONE_MATCH'].to_s.split(/\s*,\s*/)
-
etags = (cached_etags + request_etags).uniq
-
@env['HTTP_IF_NONE_MATCH'] = etags.empty? ? nil : etags.join(', ')
-
-
response = forward
-
-
if response.status == 304
-
record :valid
-
-
# Check if the response validated which is not cached here
-
etag = response.headers['ETag']
-
return response if etag && request_etags.include?(etag) && !cached_etags.include?(etag)
-
-
entry = entry.dup
-
entry.headers.delete('Date')
-
%w[Date Expires Cache-Control ETag Last-Modified].each do |name|
-
next unless value = response.headers[name]
-
entry.headers[name] = value
-
end
-
-
# even though it's empty, be sure to close the response body from upstream
-
# because middleware use close to signal end of response
-
response.body.close if response.body.respond_to?(:close)
-
-
response = entry
-
else
-
record :invalid
-
end
-
-
store(response) if response.cacheable?
-
-
response
-
end
-
-
# The cache missed or a reload is required. Forward the request to the
-
# backend and determine whether the response should be stored. This allows
-
# conditional / validation requests through to the backend but performs no
-
# caching of the response when the backend returns a 304.
-
1
def fetch
-
# send no head requests because we want content
-
@env['REQUEST_METHOD'] = 'GET'
-
-
response = forward
-
-
# Mark the response as explicitly private if any of the private
-
# request headers are present and the response was not explicitly
-
# declared public.
-
if private_request? && !response.cache_control.public?
-
response.private = true
-
elsif default_ttl > 0 && response.ttl.nil? && !response.cache_control.must_revalidate?
-
# assign a default TTL for the cache entry if none was specified in
-
# the response; the must-revalidate cache control directive disables
-
# default ttl assigment.
-
response.ttl = default_ttl
-
end
-
-
store(response) if response.cacheable?
-
-
response
-
end
-
-
# Write the response to the cache.
-
1
def store(response)
-
strip_ignore_headers(response)
-
metastore.store(@request, response, entitystore)
-
response.headers['Age'] = response.age.to_s
-
rescue Exception => e
-
log_error(e)
-
nil
-
else
-
record :store
-
end
-
-
# Remove all ignored response headers before writing to the cache.
-
1
def strip_ignore_headers(response)
-
stripped_values = ignore_headers.map { |name| response.headers.delete(name) }
-
record :ignore if stripped_values.any?
-
end
-
-
1
def log_error(exception)
-
@env['rack.errors'].write("cache error: #{exception.message}\n#{exception.backtrace.join("\n")}\n")
-
end
-
end
-
end
-
1
require 'digest/sha1'
-
-
1
module Rack::Cache
-
-
# Entity stores are used to cache response bodies across requests. All
-
# Implementations are required to calculate a SHA checksum of the data written
-
# which becomes the response body's key.
-
1
class EntityStore
-
-
# Read body calculating the SHA1 checksum and size while
-
# yielding each chunk to the block. If the body responds to close,
-
# call it after iteration is complete. Return a two-tuple of the form:
-
# [ hexdigest, size ].
-
1
def slurp(body)
-
digest, size = Digest::SHA1.new, 0
-
body.each do |part|
-
size += bytesize(part)
-
digest << part
-
yield part
-
end
-
body.close if body.respond_to? :close
-
[digest.hexdigest, size]
-
end
-
-
1
if ''.respond_to?(:bytesize)
-
1
def bytesize(string); string.bytesize; end
-
else
-
def bytesize(string); string.size; end
-
end
-
-
1
private :slurp, :bytesize
-
-
-
# Stores entity bodies on the heap using a Hash object.
-
1
class Heap < EntityStore
-
-
# Create the store with the specified backing Hash.
-
1
def initialize(hash={})
-
@hash = hash
-
end
-
-
# Determine whether the response body with the specified key (SHA1)
-
# exists in the store.
-
1
def exist?(key)
-
@hash.include?(key)
-
end
-
-
# Return an object suitable for use as a Rack response body for the
-
# specified key.
-
1
def open(key)
-
(body = @hash[key]) && body.dup
-
end
-
-
# Read all data associated with the given key and return as a single
-
# String.
-
1
def read(key)
-
(body = @hash[key]) && body.join
-
end
-
-
# Write the Rack response body immediately and return the SHA1 key.
-
1
def write(body, ttl=nil)
-
buf = []
-
key, size = slurp(body) { |part| buf << part }
-
@hash[key] = buf
-
[key, size]
-
end
-
-
# Remove the body corresponding to key; return nil.
-
1
def purge(key)
-
@hash.delete(key)
-
nil
-
end
-
-
1
def self.resolve(uri)
-
new
-
end
-
end
-
-
1
HEAP = Heap
-
1
MEM = Heap
-
-
# Stores entity bodies on disk at the specified path.
-
1
class Disk < EntityStore
-
-
# Path where entities should be stored. This directory is
-
# created the first time the store is instansiated if it does not
-
# already exist.
-
1
attr_reader :root
-
-
1
def initialize(root)
-
@root = root
-
FileUtils.mkdir_p root, :mode => 0755
-
end
-
-
1
def exist?(key)
-
File.exist?(body_path(key))
-
end
-
-
1
def read(key)
-
File.open(body_path(key), 'rb') { |f| f.read }
-
rescue Errno::ENOENT
-
nil
-
end
-
-
1
class Body < ::File #:nodoc:
-
1
def each
-
while part = read(8192)
-
yield part
-
end
-
end
-
1
alias_method :to_path, :path
-
end
-
-
# Open the entity body and return an IO object. The IO object's
-
# each method is overridden to read 8K chunks instead of lines.
-
1
def open(key)
-
Body.open(body_path(key), 'rb')
-
rescue Errno::ENOENT
-
nil
-
end
-
-
1
def write(body, ttl=nil)
-
filename = ['buf', $$, Thread.current.object_id].join('-')
-
temp_file = storage_path(filename)
-
key, size =
-
File.open(temp_file, 'wb') { |dest|
-
slurp(body) { |part| dest.write(part) }
-
}
-
-
path = body_path(key)
-
if File.exist?(path)
-
File.unlink temp_file
-
else
-
FileUtils.mkdir_p File.dirname(path), :mode => 0755
-
FileUtils.mv temp_file, path
-
end
-
[key, size]
-
end
-
-
1
def purge(key)
-
File.unlink body_path(key)
-
nil
-
rescue Errno::ENOENT
-
nil
-
end
-
-
1
protected
-
1
def storage_path(stem)
-
File.join root, stem
-
end
-
-
1
def spread(key)
-
key = key.dup
-
key[2,0] = '/'
-
key
-
end
-
-
1
def body_path(key)
-
storage_path spread(key)
-
end
-
-
1
def self.resolve(uri)
-
path = File.expand_path(uri.opaque || uri.path)
-
new path
-
end
-
end
-
-
1
DISK = Disk
-
1
FILE = Disk
-
-
# Base class for memcached entity stores.
-
1
class MemCacheBase < EntityStore
-
# The underlying Memcached instance used to communicate with the
-
# memcached daemon.
-
1
attr_reader :cache
-
-
1
extend Rack::Utils
-
-
1
def open(key)
-
data = read(key)
-
data && [data]
-
end
-
-
1
def self.resolve(uri)
-
if uri.respond_to?(:scheme)
-
server = "#{uri.host}:#{uri.port || '11211'}"
-
options = parse_query(uri.query)
-
options.keys.each do |key|
-
value =
-
case value = options.delete(key)
-
when 'true' ; true
-
when 'false' ; false
-
else value.to_sym
-
end
-
options[key.to_sym] = value
-
end
-
options[:namespace] = uri.path.sub(/^\//, '')
-
new server, options
-
else
-
# if the object provided is not a URI, pass it straight through
-
# to the underlying implementation.
-
new uri
-
end
-
end
-
end
-
-
# Uses the Dalli ruby library. This is the default unless
-
# the memcached library has already been required.
-
1
class Dalli < MemCacheBase
-
1
def initialize(server="localhost:11211", options={})
-
@cache =
-
if server.respond_to?(:stats)
-
server
-
else
-
require 'dalli'
-
::Dalli::Client.new(server, options)
-
end
-
end
-
-
1
def exist?(key)
-
!cache.get(key).nil?
-
end
-
-
1
def read(key)
-
data = cache.get(key)
-
data.force_encoding('BINARY') if data.respond_to?(:force_encoding)
-
data
-
end
-
-
1
def write(body, ttl=nil)
-
buf = StringIO.new
-
key, size = slurp(body){|part| buf.write(part) }
-
[key, size] if cache.set(key, buf.string, ttl)
-
end
-
-
1
def purge(key)
-
cache.delete(key)
-
nil
-
end
-
end
-
-
# Uses the memcached client library. The ruby based memcache-client is used
-
# in preference to this store unless the memcached library has already been
-
# required.
-
1
class MemCached < MemCacheBase
-
1
def initialize(server="localhost:11211", options={})
-
options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace)
-
@cache =
-
if server.respond_to?(:stats)
-
server
-
else
-
require 'memcached'
-
::Memcached.new(server, options)
-
end
-
end
-
-
1
def exist?(key)
-
cache.append(key, '')
-
true
-
rescue ::Memcached::NotStored
-
false
-
end
-
-
1
def read(key)
-
cache.get(key, false)
-
rescue ::Memcached::NotFound
-
nil
-
end
-
-
1
def write(body, ttl=0)
-
buf = StringIO.new
-
key, size = slurp(body){|part| buf.write(part) }
-
cache.set(key, buf.string, ttl, false)
-
[key, size]
-
end
-
-
1
def purge(key)
-
cache.delete(key)
-
nil
-
rescue ::Memcached::NotFound
-
nil
-
end
-
end
-
-
1
MEMCACHE =
-
if defined?(::Memcached)
-
MemCached
-
else
-
1
Dalli
-
end
-
-
1
MEMCACHED = MEMCACHE
-
-
1
class GAEStore < EntityStore
-
1
attr_reader :cache
-
-
1
def initialize(options = {})
-
require 'rack/cache/appengine'
-
@cache = Rack::Cache::AppEngine::MemCache.new(options)
-
end
-
-
1
def exist?(key)
-
cache.contains?(key)
-
end
-
-
1
def read(key)
-
cache.get(key)
-
end
-
-
1
def open(key)
-
if data = read(key)
-
[data]
-
else
-
nil
-
end
-
end
-
-
1
def write(body, ttl=nil)
-
buf = StringIO.new
-
key, size = slurp(body){|part| buf.write(part) }
-
cache.put(key, buf.string, ttl)
-
[key, size]
-
end
-
-
1
def purge(key)
-
cache.delete(key)
-
nil
-
end
-
-
1
def self.resolve(uri)
-
self.new(:namespace => uri.host)
-
end
-
-
end
-
-
1
GAECACHE = GAEStore
-
1
GAE = GAEStore
-
-
end
-
-
end
-
1
require 'rack/utils'
-
-
1
module Rack::Cache
-
1
class Key
-
1
include Rack::Utils
-
-
# Implement .call, since it seems like the "Rack-y" thing to do. Plus, it
-
# opens the door for cache key generators to just be blocks.
-
1
def self.call(request)
-
new(request).generate
-
end
-
-
1
def initialize(request)
-
@request = request
-
end
-
-
# Generate a normalized cache key for the request.
-
1
def generate
-
parts = []
-
parts << @request.scheme << "://"
-
parts << @request.host
-
-
if @request.scheme == "https" && @request.port != 443 ||
-
@request.scheme == "http" && @request.port != 80
-
parts << ":" << @request.port.to_s
-
end
-
-
parts << @request.script_name
-
parts << @request.path_info
-
-
if qs = query_string
-
parts << "?"
-
parts << qs
-
end
-
-
parts.join
-
end
-
-
1
private
-
# Build a normalized query string by alphabetizing all keys/values
-
# and applying consistent escaping.
-
1
def query_string
-
return nil if @request.query_string.nil?
-
-
@request.query_string.split(/[&;] */n).
-
map { |p| unescape(p).split('=', 2) }.
-
sort.
-
map { |k,v| "#{escape(k)}=#{escape(v)}" }.
-
join('&')
-
end
-
end
-
end
-
1
require 'fileutils'
-
1
require 'digest/sha1'
-
1
require 'rack/utils'
-
1
require 'rack/cache/key'
-
-
1
module Rack::Cache
-
-
# The MetaStore is responsible for storing meta information about a
-
# request/response pair keyed by the request's URL.
-
#
-
# The meta store keeps a list of request/response pairs for each canonical
-
# request URL. A request/response pair is a two element Array of the form:
-
# [request, response]
-
#
-
# The +request+ element is a Hash of Rack environment keys. Only protocol
-
# keys (i.e., those that start with "HTTP_") are stored. The +response+
-
# element is a Hash of cached HTTP response headers for the paired request.
-
#
-
# The MetaStore class is abstract and should not be instanstiated
-
# directly. Concrete subclasses should implement the protected #read,
-
# #write, and #purge methods. Care has been taken to keep these low-level
-
# methods dumb and straight-forward to implement.
-
1
class MetaStore
-
-
# Locate a cached response for the request provided. Returns a
-
# Rack::Cache::Response object if the cache hits or nil if no cache entry
-
# was found.
-
1
def lookup(request, entity_store)
-
key = cache_key(request)
-
entries = read(key)
-
-
# bail out if we have nothing cached
-
return nil if entries.empty?
-
-
# find a cached entry that matches the request.
-
env = request.env
-
match = entries.detect{|req,res| requests_match?(res['Vary'], env, req)}
-
return nil if match.nil?
-
-
_, res = match
-
if body = entity_store.open(res['X-Content-Digest'])
-
restore_response(res, body)
-
else
-
# TODO the metastore referenced an entity that doesn't exist in
-
# the entitystore. we definitely want to return nil but we should
-
# also purge the entry from the meta-store when this is detected.
-
end
-
end
-
-
# Write a cache entry to the store under the given key. Existing
-
# entries are read and any that match the response are removed.
-
# This method calls #write with the new list of cache entries.
-
1
def store(request, response, entity_store)
-
key = cache_key(request)
-
stored_env = persist_request(request)
-
-
# write the response body to the entity store if this is the
-
# original response.
-
if response.headers['X-Content-Digest'].nil?
-
if request.env['rack-cache.use_native_ttl'] && response.fresh?
-
digest, size = entity_store.write(response.body, response.ttl)
-
else
-
digest, size = entity_store.write(response.body)
-
end
-
response.headers['X-Content-Digest'] = digest
-
response.headers['Content-Length'] = size.to_s unless response.headers['Transfer-Encoding']
-
response.body = entity_store.open(digest)
-
end
-
-
# read existing cache entries, remove non-varying, and add this one to
-
# the list
-
vary = response.vary
-
entries =
-
read(key).reject do |env,res|
-
(vary == res['Vary']) &&
-
requests_match?(vary, env, stored_env)
-
end
-
-
headers = persist_response(response)
-
headers.delete 'Age'
-
-
entries.unshift [stored_env, headers]
-
write key, entries
-
key
-
end
-
-
# Generate a cache key for the request.
-
1
def cache_key(request)
-
keygen = request.env['rack-cache.cache_key'] || Key
-
keygen.call(request)
-
end
-
-
# Invalidate all cache entries that match the request.
-
1
def invalidate(request, entity_store)
-
modified = false
-
key = cache_key(request)
-
entries =
-
read(key).map do |req, res|
-
response = restore_response(res)
-
if response.fresh?
-
response.expire!
-
modified = true
-
[req, persist_response(response)]
-
else
-
[req, res]
-
end
-
end
-
write key, entries if modified
-
end
-
-
1
private
-
-
# Extract the environment Hash from +request+ while making any
-
# necessary modifications in preparation for persistence. The Hash
-
# returned must be marshalable.
-
1
def persist_request(request)
-
env = request.env.dup
-
env.reject! { |key,val| key =~ /[^0-9A-Z_]/ || !val.respond_to?(:to_str) }
-
env
-
end
-
-
# Converts a stored response hash into a Response object. The caller
-
# is responsible for loading and passing the body if needed.
-
1
def restore_response(hash, body=nil)
-
status = hash.delete('X-Status').to_i
-
Rack::Cache::Response.new(status, hash, body)
-
end
-
-
1
def persist_response(response)
-
hash = response.headers.to_hash
-
hash['X-Status'] = response.status.to_s
-
hash
-
end
-
-
# Determine whether the two environment hashes are non-varying based on
-
# the vary response header value provided.
-
1
def requests_match?(vary, env1, env2)
-
return true if vary.nil? || vary == ''
-
vary.split(/[\s,]+/).all? do |header|
-
key = "HTTP_#{header.upcase.tr('-', '_')}"
-
env1[key] == env2[key]
-
end
-
end
-
-
1
protected
-
# Locate all cached request/response pairs that match the specified
-
# URL key. The result must be an Array of all cached request/response
-
# pairs. An empty Array must be returned if nothing is cached for
-
# the specified key.
-
1
def read(key)
-
raise NotImplemented
-
end
-
-
# Store an Array of request/response pairs for the given key. Concrete
-
# implementations should not attempt to filter or concatenate the
-
# list in any way.
-
1
def write(key, negotiations)
-
raise NotImplemented
-
end
-
-
# Remove all cached entries at the key specified. No error is raised
-
# when the key does not exist.
-
1
def purge(key)
-
raise NotImplemented
-
end
-
-
1
private
-
# Generate a SHA1 hex digest for the specified string. This is a
-
# simple utility method for meta store implementations.
-
1
def hexdigest(data)
-
Digest::SHA1.hexdigest(data)
-
end
-
-
1
public
-
# Concrete MetaStore implementation that uses a simple Hash to store
-
# request/response pairs on the heap.
-
1
class Heap < MetaStore
-
1
def initialize(hash={})
-
@hash = hash
-
end
-
-
1
def read(key)
-
if data = @hash[key]
-
Marshal.load(data)
-
else
-
[]
-
end
-
end
-
-
1
def write(key, entries)
-
@hash[key] = Marshal.dump(entries)
-
end
-
-
1
def purge(key)
-
@hash.delete(key)
-
nil
-
end
-
-
1
def to_hash
-
@hash
-
end
-
-
1
def self.resolve(uri)
-
new
-
end
-
end
-
-
1
HEAP = Heap
-
1
MEM = HEAP
-
-
# Concrete MetaStore implementation that stores request/response
-
# pairs on disk.
-
1
class Disk < MetaStore
-
1
attr_reader :root
-
-
1
def initialize(root="/tmp/rack-cache/meta-#{ARGV[0]}")
-
@root = File.expand_path(root)
-
FileUtils.mkdir_p(root, :mode => 0755)
-
end
-
-
1
def read(key)
-
path = key_path(key)
-
File.open(path, 'rb') { |io| Marshal.load(io) }
-
rescue Errno::ENOENT, IOError
-
[]
-
end
-
-
1
def write(key, entries)
-
tries = 0
-
begin
-
path = key_path(key)
-
File.open(path, 'wb') { |io| Marshal.dump(entries, io, -1) }
-
rescue Errno::ENOENT, IOError
-
Dir.mkdir(File.dirname(path), 0755)
-
retry if (tries += 1) == 1
-
end
-
end
-
-
1
def purge(key)
-
path = key_path(key)
-
File.unlink(path)
-
nil
-
rescue Errno::ENOENT, IOError
-
nil
-
end
-
-
1
private
-
1
def key_path(key)
-
File.join(root, spread(hexdigest(key)))
-
end
-
-
1
def spread(sha, n=2)
-
sha = sha.dup
-
sha[n,0] = '/'
-
sha
-
end
-
-
1
public
-
1
def self.resolve(uri)
-
path = File.expand_path(uri.opaque || uri.path)
-
new path
-
end
-
-
end
-
-
1
DISK = Disk
-
1
FILE = Disk
-
-
# Stores request/response pairs in memcached. Keys are not stored
-
# directly since memcached has a 250-byte limit on key names. Instead,
-
# the SHA1 hexdigest of the key is used.
-
1
class MemCacheBase < MetaStore
-
1
extend Rack::Utils
-
-
# The MemCache object used to communicated with the memcached
-
# daemon.
-
1
attr_reader :cache
-
-
# Create MemCache store for the given URI. The URI must specify
-
# a host and may specify a port, namespace, and options:
-
#
-
# memcached://example.com:11211/namespace?opt1=val1&opt2=val2
-
#
-
# Query parameter names and values are documented with the memcached
-
# library: http://tinyurl.com/4upqnd
-
1
def self.resolve(uri)
-
if uri.respond_to?(:scheme)
-
server = "#{uri.host}:#{uri.port || '11211'}"
-
options = parse_query(uri.query)
-
options.keys.each do |key|
-
value =
-
case value = options.delete(key)
-
when 'true' ; true
-
when 'false' ; false
-
else value.to_sym
-
end
-
options[key.to_sym] = value
-
end
-
-
options[:namespace] = uri.path.to_s.sub(/^\//, '')
-
-
new server, options
-
else
-
# if the object provided is not a URI, pass it straight through
-
# to the underlying implementation.
-
new uri
-
end
-
end
-
end
-
-
1
class Dalli < MemCacheBase
-
1
def initialize(server="localhost:11211", options={})
-
@cache =
-
if server.respond_to?(:stats)
-
server
-
else
-
require 'dalli'
-
::Dalli::Client.new(server, options)
-
end
-
end
-
-
1
def read(key)
-
key = hexdigest(key)
-
cache.get(key) || []
-
end
-
-
1
def write(key, entries)
-
key = hexdigest(key)
-
cache.set(key, entries)
-
end
-
-
1
def purge(key)
-
cache.delete(hexdigest(key))
-
nil
-
end
-
end
-
-
1
class MemCached < MemCacheBase
-
# The Memcached instance used to communicated with the memcached
-
# daemon.
-
1
attr_reader :cache
-
-
1
def initialize(server="localhost:11211", options={})
-
options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace)
-
@cache =
-
if server.respond_to?(:stats)
-
server
-
else
-
require 'memcached'
-
Memcached.new(server, options)
-
end
-
end
-
-
1
def read(key)
-
key = hexdigest(key)
-
cache.get(key)
-
rescue Memcached::NotFound
-
[]
-
end
-
-
1
def write(key, entries)
-
key = hexdigest(key)
-
cache.set(key, entries)
-
end
-
-
1
def purge(key)
-
key = hexdigest(key)
-
cache.delete(key)
-
nil
-
rescue Memcached::NotFound
-
nil
-
end
-
end
-
-
1
MEMCACHE =
-
if defined?(::Memcached)
-
MemCached
-
else
-
1
Dalli
-
end
-
1
MEMCACHED = MEMCACHE
-
-
1
class GAEStore < MetaStore
-
1
attr_reader :cache
-
-
1
def initialize(options = {})
-
require 'rack/cache/appengine'
-
@cache = Rack::Cache::AppEngine::MemCache.new(options)
-
end
-
-
1
def read(key)
-
key = hexdigest(key)
-
cache.get(key) || []
-
end
-
-
1
def write(key, entries)
-
key = hexdigest(key)
-
cache.put(key, entries)
-
end
-
-
1
def purge(key)
-
key = hexdigest(key)
-
cache.delete(key)
-
nil
-
end
-
-
1
def self.resolve(uri)
-
self.new(:namespace => uri.host)
-
end
-
-
end
-
-
1
GAECACHE = GAEStore
-
1
GAE = GAEStore
-
-
end
-
-
end
-
1
require 'rack/cache/key'
-
1
require 'rack/cache/storage'
-
-
1
module Rack::Cache
-
-
# Configuration options and utility methods for option access. Rack::Cache
-
# uses the Rack Environment to store option values. All options documented
-
# below are stored in the Rack Environment as "rack-cache.<option>", where
-
# <option> is the option name.
-
1
module Options
-
1
extend self
-
-
1
def self.option_accessor(key)
-
11
name = option_name(key)
-
11
define_method(key) { || options[name] }
-
11
define_method("#{key}=") { |value| options[name] = value }
-
11
define_method("#{key}?") { || !! options[name] }
-
end
-
-
1
def option_name(key)
-
11
case key
-
11
when Symbol ; "rack-cache.#{key}"
-
when String ; key
-
else raise ArgumentError
-
end
-
end
-
1
module_function :option_name
-
-
# Enable verbose trace logging. This option is currently enabled by
-
# default but is likely to be disabled in a future release.
-
1
option_accessor :verbose
-
-
# The storage resolver. Defaults to the Rack::Cache.storage singleton instance
-
# of Rack::Cache::Storage. This object is responsible for resolving metastore
-
# and entitystore URIs to an implementation instances.
-
1
option_accessor :storage
-
-
# A URI specifying the meta-store implementation that should be used to store
-
# request/response meta information. The following URIs schemes are
-
# supported:
-
#
-
# * heap:/
-
# * file:/absolute/path or file:relative/path
-
# * memcached://localhost:11211[/namespace]
-
#
-
# If no meta store is specified the 'heap:/' store is assumed. This
-
# implementation has significant draw-backs so explicit configuration is
-
# recommended.
-
1
option_accessor :metastore
-
-
# A custom cache key generator, which can be anything that responds to :call.
-
# By default, this is the Rack::Cache::Key class, but you can implement your
-
# own generator. A cache key generator gets passed a request and generates the
-
# appropriate cache key.
-
#
-
# In addition to setting the generator to an object, you can just pass a block
-
# instead, which will act as the cache key generator:
-
#
-
# set :cache_key do |request|
-
# request.fullpath.replace(/\//, '-')
-
# end
-
1
option_accessor :cache_key
-
-
# A URI specifying the entity-store implementation that should be used to
-
# store response bodies. See the metastore option for information on
-
# supported URI schemes.
-
#
-
# If no entity store is specified the 'heap:/' store is assumed. This
-
# implementation has significant draw-backs so explicit configuration is
-
# recommended.
-
1
option_accessor :entitystore
-
-
# The number of seconds that a cache entry should be considered
-
# "fresh" when no explicit freshness information is provided in
-
# a response. Explicit Cache-Control or Expires headers
-
# override this value.
-
#
-
# Default: 0
-
1
option_accessor :default_ttl
-
-
# Set of response headers that are removed before storing them in the
-
# cache. These headers are only removed for cacheable responses. For
-
# example, in most cases, it makes sense to prevent cookies from being
-
# stored in the cache.
-
#
-
# Default: ['Set-Cookie']
-
1
option_accessor :ignore_headers
-
-
# Set of request headers that trigger "private" cache-control behavior
-
# on responses that don't explicitly state whether the response is
-
# public or private via a Cache-Control directive. Applications that use
-
# cookies for authorization may need to add the 'Cookie' header to this
-
# list.
-
#
-
# Default: ['Authorization', 'Cookie']
-
1
option_accessor :private_headers
-
-
# Specifies whether the client can force a cache reload by including a
-
# Cache-Control "no-cache" directive in the request. This is enabled by
-
# default for compliance with RFC 2616.
-
1
option_accessor :allow_reload
-
-
# Specifies whether the client can force a cache revalidate by including
-
# a Cache-Control "max-age=0" directive in the request. This is enabled by
-
# default for compliance with RFC 2616.
-
1
option_accessor :allow_revalidate
-
-
# Specifies whether the underlying entity store's native expiration should
-
# be used.
-
1
option_accessor :use_native_ttl
-
-
# The underlying options Hash. During initialization (or outside of a
-
# request), this is a default values Hash. During a request, this is the
-
# Rack environment Hash. The default values Hash is merged in underneath
-
# the Rack environment before each request is processed.
-
1
def options
-
@env || @default_options
-
end
-
-
# Set multiple options.
-
1
def options=(hash={})
-
hash.each { |key,value| write_option(key, value) }
-
end
-
-
# Set an option. When +option+ is a Symbol, it is set in the Rack
-
# Environment as "rack-cache.option". When +option+ is a String, it
-
# exactly as specified. The +option+ argument may also be a Hash in
-
# which case each key/value pair is merged into the environment as if
-
# the #set method were called on each.
-
1
def set(option, value=self, &block)
-
if block_given?
-
write_option option, block
-
elsif value == self
-
self.options = option.to_hash
-
else
-
write_option option, value
-
end
-
end
-
-
1
private
-
1
def initialize_options(options={})
-
@default_options = {
-
'rack-cache.cache_key' => Key,
-
'rack-cache.verbose' => true,
-
'rack-cache.storage' => Rack::Cache::Storage.instance,
-
'rack-cache.metastore' => 'heap:/',
-
'rack-cache.entitystore' => 'heap:/',
-
'rack-cache.default_ttl' => 0,
-
'rack-cache.ignore_headers' => ['Set-Cookie'],
-
'rack-cache.private_headers' => ['Authorization', 'Cookie'],
-
'rack-cache.allow_reload' => false,
-
'rack-cache.allow_revalidate' => false,
-
'rack-cache.use_native_ttl' => false,
-
}
-
self.options = options
-
end
-
-
1
def read_option(key)
-
options[option_name(key)]
-
end
-
-
1
def write_option(key, value)
-
options[option_name(key)] = value
-
end
-
end
-
end
-
1
require 'rack/request'
-
1
require 'rack/cache/cachecontrol'
-
-
1
module Rack::Cache
-
-
# Provides access to the HTTP request. The +request+ and +original_request+
-
# objects exposed by the Core caching engine are instances of this class.
-
#
-
# Request objects respond to a variety of convenience methods, including
-
# everything defined by Rack::Request as well as the Headers and
-
# RequestHeaders modules.
-
1
class Request < Rack::Request
-
# The HTTP request method. This is the standard implementation of this
-
# method but is respecified here due to libraries that attempt to modify
-
# the behavior to respect POST tunnel method specifiers. We always want
-
# the real request method.
-
1
def request_method
-
@env['REQUEST_METHOD']
-
end
-
-
# A CacheControl instance based on the request's Cache-Control header.
-
1
def cache_control
-
@cache_control ||= CacheControl.new(env['HTTP_CACHE_CONTROL'])
-
end
-
-
# True when the Cache-Control/no-cache directive is present or the
-
# Pragma header is set to no-cache.
-
1
def no_cache?
-
cache_control['no-cache'] ||
-
env['HTTP_PRAGMA'] == 'no-cache'
-
end
-
end
-
end
-
1
require 'time'
-
1
require 'set'
-
1
require 'rack/response'
-
1
require 'rack/utils'
-
1
require 'rack/cache/cachecontrol'
-
-
1
module Rack::Cache
-
-
# Provides access to the response generated by the downstream application. The
-
# +response+, +original_response+, and +entry+ objects exposed by the Core
-
# caching engine are instances of this class.
-
#
-
# Response objects respond to a variety of convenience methods, including
-
# those defined in Rack::Response::Helpers, Rack::Cache::Headers,
-
# and Rack::Cache::ResponseHeaders.
-
#
-
# Note that Rack::Cache::Response is not a subclass of Rack::Response and does
-
# not perform many of the same initialization and finalization tasks. For
-
# example, the body is not slurped during initialization and there are no
-
# facilities for generating response output.
-
1
class Response
-
1
include Rack::Response::Helpers
-
-
# Rack response tuple accessors.
-
1
attr_accessor :status, :headers, :body
-
-
# The time when the Response object was instantiated.
-
1
attr_reader :now
-
-
# Create a Response instance given the response status code, header hash,
-
# and body.
-
1
def initialize(status, headers, body)
-
@status = status.to_i
-
@headers = Rack::Utils::HeaderHash.new(headers)
-
@body = body
-
@now = Time.now
-
@headers['Date'] ||= @now.httpdate
-
end
-
-
1
def initialize_copy(other)
-
super
-
@headers = other.headers.dup
-
end
-
-
# Return the status, headers, and body in a three-tuple.
-
1
def to_a
-
[status, headers.to_hash, body]
-
end
-
-
# Status codes of responses that MAY be stored by a cache or used in reply
-
# to a subsequent request.
-
#
-
# http://tools.ietf.org/html/rfc2616#section-13.4
-
1
CACHEABLE_RESPONSE_CODES = [
-
200, # OK
-
203, # Non-Authoritative Information
-
300, # Multiple Choices
-
301, # Moved Permanently
-
302, # Found
-
404, # Not Found
-
410 # Gone
-
].to_set
-
-
# A Hash of name=value pairs that correspond to the Cache-Control header.
-
# Valueless parameters (e.g., must-revalidate, no-store) have a Hash value
-
# of true. This method always returns a Hash, empty if no Cache-Control
-
# header is present.
-
1
def cache_control
-
@cache_control ||= CacheControl.new(headers['Cache-Control'])
-
end
-
-
# Set the Cache-Control header to the values specified by the Hash. See
-
# the #cache_control method for information on expected Hash structure.
-
1
def cache_control=(value)
-
if value.respond_to? :to_hash
-
cache_control.clear
-
cache_control.merge!(value)
-
value = cache_control.to_s
-
end
-
-
if value.nil? || value.empty?
-
headers.delete('Cache-Control')
-
else
-
headers['Cache-Control'] = value
-
end
-
end
-
-
# Determine if the response is "fresh". Fresh responses may be served from
-
# cache without any interaction with the origin. A response is considered
-
# fresh when it includes a Cache-Control/max-age indicator or Expiration
-
# header and the calculated age is less than the freshness lifetime.
-
1
def fresh?
-
ttl && ttl > 0
-
end
-
-
# Determine if the response is worth caching under any circumstance. Responses
-
# marked "private" with an explicit Cache-Control directive are considered
-
# uncacheable
-
#
-
# Responses with neither a freshness lifetime (Expires, max-age) nor cache
-
# validator (Last-Modified, ETag) are considered uncacheable.
-
1
def cacheable?
-
return false unless CACHEABLE_RESPONSE_CODES.include?(status)
-
return false if cache_control.no_store? || cache_control.private?
-
validateable? || fresh?
-
end
-
-
# Determine if the response includes headers that can be used to validate
-
# the response with the origin using a conditional GET request.
-
1
def validateable?
-
headers.key?('Last-Modified') || headers.key?('ETag')
-
end
-
-
# Mark the response "private", making it ineligible for serving other
-
# clients.
-
1
def private=(value)
-
value = value ? true : nil
-
self.cache_control = cache_control.
-
merge('public' => !value, 'private' => value)
-
end
-
-
# Indicates that the cache must not serve a stale response in any
-
# circumstance without first revalidating with the origin. When present,
-
# the TTL of the response should not be overriden to be greater than the
-
# value provided by the origin.
-
1
def must_revalidate?
-
cache_control.must_revalidate || cache_control.proxy_revalidate
-
end
-
-
# Mark the response stale by setting the Age header to be equal to the
-
# maximum age of the response.
-
1
def expire!
-
headers['Age'] = max_age.to_s if fresh?
-
end
-
-
# The date, as specified by the Date header. When no Date header is present,
-
# set the Date header to Time.now and return.
-
1
def date
-
if date = headers['Date']
-
Time.httpdate(date)
-
else
-
headers['Date'] = now.httpdate unless headers.frozen?
-
now
-
end
-
end
-
-
# The age of the response.
-
1
def age
-
(headers['Age'] || [(now - date).to_i, 0].max).to_i
-
end
-
-
# The number of seconds after the time specified in the response's Date
-
# header when the the response should no longer be considered fresh. First
-
# check for a s-maxage directive, then a max-age directive, and then fall
-
# back on an expires header; return nil when no maximum age can be
-
# established.
-
1
def max_age
-
cache_control.shared_max_age ||
-
cache_control.max_age ||
-
(expires && (expires - date))
-
end
-
-
# The value of the Expires header as a Time object.
-
1
def expires
-
headers['Expires'] && Time.httpdate(headers['Expires'])
-
end
-
-
# The number of seconds after which the response should no longer
-
# be considered fresh. Sets the Cache-Control max-age directive.
-
1
def max_age=(value)
-
self.cache_control = cache_control.merge('max-age' => value.to_s)
-
end
-
-
# Like #max_age= but sets the s-maxage directive, which applies only
-
# to shared caches.
-
1
def shared_max_age=(value)
-
self.cache_control = cache_control.merge('s-maxage' => value.to_s)
-
end
-
-
# The response's time-to-live in seconds, or nil when no freshness
-
# information is present in the response. When the responses #ttl
-
# is <= 0, the response may not be served from cache without first
-
# revalidating with the origin.
-
1
def ttl
-
max_age - age if max_age
-
end
-
-
# Set the response's time-to-live for shared caches to the specified number
-
# of seconds. This adjusts the Cache-Control/s-maxage directive.
-
1
def ttl=(seconds)
-
self.shared_max_age = age + seconds
-
end
-
-
# Set the response's time-to-live for private/client caches. This adjusts
-
# the Cache-Control/max-age directive.
-
1
def client_ttl=(seconds)
-
self.max_age = age + seconds
-
end
-
-
# The String value of the Last-Modified header exactly as it appears
-
# in the response (i.e., no date parsing / conversion is performed).
-
1
def last_modified
-
headers['Last-Modified']
-
end
-
-
# The literal value of ETag HTTP header or nil if no ETag is specified.
-
1
def etag
-
headers['ETag']
-
end
-
-
# Headers that MUST NOT be included with 304 Not Modified responses.
-
#
-
# http://tools.ietf.org/html/rfc2616#section-10.3.5
-
1
NOT_MODIFIED_OMIT_HEADERS = %w[
-
Allow
-
Content-Encoding
-
Content-Language
-
Content-Length
-
Content-MD5
-
Content-Type
-
Last-Modified
-
].to_set
-
-
# Modify the response so that it conforms to the rules defined for
-
# '304 Not Modified'. This sets the status, removes the body, and
-
# discards any headers that MUST NOT be included in 304 responses.
-
#
-
# http://tools.ietf.org/html/rfc2616#section-10.3.5
-
1
def not_modified!
-
body.close if body.respond_to?(:close)
-
self.status = 304
-
self.body = []
-
NOT_MODIFIED_OMIT_HEADERS.each { |name| headers.delete(name) }
-
nil
-
end
-
-
# The literal value of the Vary header, or nil when no header is present.
-
1
def vary
-
headers['Vary']
-
end
-
-
# Does the response include a Vary header?
-
1
def vary?
-
! vary.nil?
-
end
-
-
# An array of header names given in the Vary header or an empty
-
# array when no Vary header is present.
-
1
def vary_header_names
-
return [] unless vary = headers['Vary']
-
vary.split(/[\s,]+/)
-
end
-
-
end
-
end
-
1
require 'uri'
-
1
require 'rack/cache/metastore'
-
1
require 'rack/cache/entitystore'
-
-
1
module Rack::Cache
-
-
# Maintains a collection of MetaStore and EntityStore instances keyed by
-
# URI. A single instance of this class can be used across a single process
-
# to ensure that only a single instance of a backing store is created per
-
# unique storage URI.
-
1
class Storage
-
1
def initialize
-
1
@metastores = {}
-
1
@entitystores = {}
-
end
-
-
1
def resolve_metastore_uri(uri)
-
@metastores[uri.to_s] ||= create_store(MetaStore, uri)
-
end
-
-
1
def resolve_entitystore_uri(uri)
-
@entitystores[uri.to_s] ||= create_store(EntityStore, uri)
-
end
-
-
1
def clear
-
@metastores.clear
-
@entitystores.clear
-
nil
-
end
-
-
1
private
-
1
def create_store(type, uri)
-
if uri.respond_to?(:scheme) || uri.respond_to?(:to_str)
-
uri = URI.parse(uri) unless uri.respond_to?(:scheme)
-
if type.const_defined?(uri.scheme.upcase)
-
klass = type.const_get(uri.scheme.upcase)
-
klass.resolve(uri)
-
else
-
fail "Unknown storage provider: #{uri.to_s}"
-
end
-
else
-
# hack in support for passing a Dalli::Client or Memcached object
-
# as the storage URI.
-
case
-
when defined?(::Dalli) && uri.kind_of?(::Dalli::Client)
-
type.const_get(:Dalli).resolve(uri)
-
when defined?(::Memcached) && uri.respond_to?(:stats)
-
type.const_get(:MemCached).resolve(uri)
-
else
-
fail "Unknown storage provider: #{uri.to_s}"
-
end
-
end
-
end
-
-
1
public
-
1
@@singleton_instance = new
-
1
def self.instance
-
@@singleton_instance
-
end
-
end
-
-
end
-
# Define a package task library to aid in the definition of
-
# redistributable package files.
-
-
1
require 'rake'
-
1
require 'rake/tasklib'
-
-
1
module Rake
-
-
# Create a packaging task that will package the project into
-
# distributable files (e.g zip archive or tar files).
-
#
-
# The PackageTask will create the following targets:
-
#
-
# [<b>:package</b>]
-
# Create all the requested package files.
-
#
-
# [<b>:clobber_package</b>]
-
# Delete all the package files. This target is automatically
-
# added to the main clobber target.
-
#
-
# [<b>:repackage</b>]
-
# Rebuild the package files from scratch, even if they are not out
-
# of date.
-
#
-
# [<b>"<em>package_dir</em>/<em>name</em>-<em>version</em>.tgz"</b>]
-
# Create a gzipped tar package (if <em>need_tar</em> is true).
-
#
-
# [<b>"<em>package_dir</em>/<em>name</em>-<em>version</em>.tar.gz"</b>]
-
# Create a gzipped tar package (if <em>need_tar_gz</em> is true).
-
#
-
# [<b>"<em>package_dir</em>/<em>name</em>-<em>version</em>.tar.bz2"</b>]
-
# Create a bzip2'd tar package (if <em>need_tar_bz2</em> is true).
-
#
-
# [<b>"<em>package_dir</em>/<em>name</em>-<em>version</em>.zip"</b>]
-
# Create a zip package archive (if <em>need_zip</em> is true).
-
#
-
# Example:
-
#
-
# Rake::PackageTask.new("rake", "1.2.3") do |p|
-
# p.need_tar = true
-
# p.package_files.include("lib/**/*.rb")
-
# end
-
#
-
1
class PackageTask < TaskLib
-
# Name of the package (from the GEM Spec).
-
1
attr_accessor :name
-
-
# Version of the package (e.g. '1.3.2').
-
1
attr_accessor :version
-
-
# Directory used to store the package files (default is 'pkg').
-
1
attr_accessor :package_dir
-
-
# True if a gzipped tar file (tgz) should be produced (default is false).
-
1
attr_accessor :need_tar
-
-
# True if a gzipped tar file (tar.gz) should be produced (default is false).
-
1
attr_accessor :need_tar_gz
-
-
# True if a bzip2'd tar file (tar.bz2) should be produced (default is false).
-
1
attr_accessor :need_tar_bz2
-
-
# True if a zip file should be produced (default is false)
-
1
attr_accessor :need_zip
-
-
# List of files to be included in the package.
-
1
attr_accessor :package_files
-
-
# Tar command for gzipped or bzip2ed archives. The default is 'tar'.
-
1
attr_accessor :tar_command
-
-
# Zip command for zipped archives. The default is 'zip'.
-
1
attr_accessor :zip_command
-
-
# Create a Package Task with the given name and version. Use +:noversion+
-
# as the version to build a package without a version or to provide a
-
# fully-versioned package name.
-
-
1
def initialize(name=nil, version=nil)
-
init(name, version)
-
yield self if block_given?
-
define unless name.nil?
-
end
-
-
# Initialization that bypasses the "yield self" and "define" step.
-
1
def init(name, version)
-
1
@name = name
-
1
@version = version
-
1
@package_files = Rake::FileList.new
-
1
@package_dir = 'pkg'
-
1
@need_tar = false
-
1
@need_tar_gz = false
-
1
@need_tar_bz2 = false
-
1
@need_zip = false
-
1
@tar_command = 'tar'
-
1
@zip_command = 'zip'
-
end
-
-
# Create the tasks defined by this task library.
-
1
def define
-
1
fail "Version required (or :noversion)" if @version.nil?
-
1
@version = nil if :noversion == @version
-
-
1
desc "Build all the packages"
-
1
task :package
-
-
1
desc "Force a rebuild of the package files"
-
1
task :repackage => [:clobber_package, :package]
-
-
1
desc "Remove package products"
-
1
task :clobber_package do
-
rm_r package_dir rescue nil
-
end
-
-
1
task :clobber => [:clobber_package]
-
-
[
-
1
[need_tar, tgz_file, "z"],
-
[need_tar_gz, tar_gz_file, "z"],
-
[need_tar_bz2, tar_bz2_file, "j"]
-
].each do |(need, file, flag)|
-
3
if need
-
task :package => ["#{package_dir}/#{file}"]
-
file "#{package_dir}/#{file}" => [package_dir_path] + package_files do
-
chdir(package_dir) do
-
sh %{#{@tar_command} #{flag}cvf #{file} #{package_name}}
-
end
-
end
-
end
-
end
-
-
1
if need_zip
-
task :package => ["#{package_dir}/#{zip_file}"]
-
file "#{package_dir}/#{zip_file}" => [package_dir_path] + package_files do
-
chdir(package_dir) do
-
sh %{#{@zip_command} -r #{zip_file} #{package_name}}
-
end
-
end
-
end
-
-
1
directory package_dir
-
-
1
file package_dir_path => @package_files do
-
mkdir_p package_dir rescue nil
-
@package_files.each do |fn|
-
f = File.join(package_dir_path, fn)
-
fdir = File.dirname(f)
-
mkdir_p(fdir) if !File.exist?(fdir)
-
if File.directory?(fn)
-
mkdir_p(f)
-
else
-
rm_f f
-
safe_ln(fn, f)
-
end
-
end
-
end
-
1
self
-
end
-
-
1
def package_name
-
4
@version ? "#{@name}-#{@version}" : @name
-
end
-
-
1
def package_dir_path
-
1
"#{package_dir}/#{package_name}"
-
end
-
-
1
def tgz_file
-
1
"#{package_name}.tgz"
-
end
-
-
1
def tar_gz_file
-
1
"#{package_name}.tar.gz"
-
end
-
-
1
def tar_bz2_file
-
1
"#{package_name}.tar.bz2"
-
end
-
-
1
def zip_file
-
"#{package_name}.zip"
-
end
-
end
-
-
end
-
1
require 'rake'
-
-
1
module Rake
-
-
# Base class for Task Libraries.
-
1
class TaskLib
-
1
include Cloneable
-
1
include Rake::DSL
-
-
# Make a symbol by pasting two strings together.
-
#
-
# NOTE: DEPRECATED! This method is kinda stupid. I don't know why
-
# I didn't just use string interpolation. But now other task
-
# libraries depend on this so I can't remove it without breaking
-
# other people's code. So for now it stays for backwards
-
# compatibility. BUT DON'T USE IT.
-
1
def paste(a,b) # :nodoc:
-
(a.to_s + b.to_s).intern
-
end
-
end
-
-
end
-
# support multiple ruby version (fat binaries under windows)
-
1
begin
-
1
RUBY_VERSION =~ /(\d+\.\d+)/
-
1
require "sqlite3/#{$1}/sqlite3_native"
-
rescue LoadError
-
1
require 'sqlite3/sqlite3_native'
-
end
-
-
1
require 'sqlite3/database'
-
1
require 'sqlite3/version'
-
2
module SQLite3 ; module Constants
-
-
1
module TextRep
-
1
UTF8 = 1
-
1
UTF16LE = 2
-
1
UTF16BE = 3
-
1
UTF16 = 4
-
1
ANY = 5
-
end
-
-
1
module ColumnType
-
1
INTEGER = 1
-
1
FLOAT = 2
-
1
TEXT = 3
-
1
BLOB = 4
-
1
NULL = 5
-
end
-
-
1
module ErrorCode
-
1
OK = 0 # Successful result
-
1
ERROR = 1 # SQL error or missing database
-
1
INTERNAL = 2 # An internal logic error in SQLite
-
1
PERM = 3 # Access permission denied
-
1
ABORT = 4 # Callback routine requested an abort
-
1
BUSY = 5 # The database file is locked
-
1
LOCKED = 6 # A table in the database is locked
-
1
NOMEM = 7 # A malloc() failed
-
1
READONLY = 8 # Attempt to write a readonly database
-
1
INTERRUPT = 9 # Operation terminated by sqlite_interrupt()
-
1
IOERR = 10 # Some kind of disk I/O error occurred
-
1
CORRUPT = 11 # The database disk image is malformed
-
1
NOTFOUND = 12 # (Internal Only) Table or record not found
-
1
FULL = 13 # Insertion failed because database is full
-
1
CANTOPEN = 14 # Unable to open the database file
-
1
PROTOCOL = 15 # Database lock protocol error
-
1
EMPTY = 16 # (Internal Only) Database table is empty
-
1
SCHEMA = 17 # The database schema changed
-
1
TOOBIG = 18 # Too much data for one row of a table
-
1
CONSTRAINT = 19 # Abort due to contraint violation
-
1
MISMATCH = 20 # Data type mismatch
-
1
MISUSE = 21 # Library used incorrectly
-
1
NOLFS = 22 # Uses OS features not supported on host
-
1
AUTH = 23 # Authorization denied
-
-
1
ROW = 100 # sqlite_step() has another row ready
-
1
DONE = 101 # sqlite_step() has finished executing
-
end
-
-
end ; end
-
1
require 'sqlite3/constants'
-
1
require 'sqlite3/errors'
-
1
require 'sqlite3/pragmas'
-
1
require 'sqlite3/statement'
-
1
require 'sqlite3/translator'
-
1
require 'sqlite3/value'
-
-
1
module SQLite3
-
-
# The Database class encapsulates a single connection to a SQLite3 database.
-
# Its usage is very straightforward:
-
#
-
# require 'sqlite3'
-
#
-
# SQLite3::Database.new( "data.db" ) do |db|
-
# db.execute( "select * from table" ) do |row|
-
# p row
-
# end
-
# end
-
#
-
# It wraps the lower-level methods provides by the selected driver, and
-
# includes the Pragmas module for access to various pragma convenience
-
# methods.
-
#
-
# The Database class provides type translation services as well, by which
-
# the SQLite3 data types (which are all represented as strings) may be
-
# converted into their corresponding types (as defined in the schemas
-
# for their tables). This translation only occurs when querying data from
-
# the database--insertions and updates are all still typeless.
-
#
-
# Furthermore, the Database class has been designed to work well with the
-
# ArrayFields module from Ara Howard. If you require the ArrayFields
-
# module before performing a query, and if you have not enabled results as
-
# hashes, then the results will all be indexible by field name.
-
1
class Database
-
1
attr_reader :collations
-
-
1
include Pragmas
-
-
1
class << self
-
-
1
alias :open :new
-
-
# Quotes the given string, making it safe to use in an SQL statement.
-
# It replaces all instances of the single-quote character with two
-
# single-quote characters. The modified string is returned.
-
1
def quote( string )
-
string.gsub( /'/, "''" )
-
end
-
-
end
-
-
# A boolean that indicates whether rows in result sets should be returned
-
# as hashes or not. By default, rows are returned as arrays.
-
1
attr_accessor :results_as_hash
-
-
1
def type_translation= value # :nodoc:
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling SQLite3::Database#type_translation=
-
SQLite3::Database#type_translation= is deprecated and will be removed
-
in version 2.0.0.
-
eowarn
-
@type_translation = value
-
end
-
1
attr_reader :type_translation # :nodoc:
-
-
# Return the type translator employed by this database instance. Each
-
# database instance has its own type translator; this allows for different
-
# type handlers to be installed in each instance without affecting other
-
# instances. Furthermore, the translators are instantiated lazily, so that
-
# if a database does not use type translation, it will not be burdened by
-
# the overhead of a useless type translator. (See the Translator class.)
-
1
def translator
-
@translator ||= Translator.new
-
end
-
-
# Installs (or removes) a block that will be invoked for every access
-
# to the database. If the block returns 0 (or +nil+), the statement
-
# is allowed to proceed. Returning 1 causes an authorization error to
-
# occur, and returning 2 causes the access to be silently denied.
-
1
def authorizer( &block )
-
self.authorizer = block
-
end
-
-
# Returns a Statement object representing the given SQL. This does not
-
# execute the statement; it merely prepares the statement for execution.
-
#
-
# The Statement can then be executed using Statement#execute.
-
#
-
1
def prepare sql
-
7
stmt = SQLite3::Statement.new( self, sql )
-
7
return stmt unless block_given?
-
-
7
begin
-
7
yield stmt
-
ensure
-
7
stmt.close
-
end
-
end
-
-
# Executes the given SQL statement. If additional parameters are given,
-
# they are treated as bind variables, and are bound to the placeholders in
-
# the query.
-
#
-
# Note that if any of the values passed to this are hashes, then the
-
# key/value pairs are each bound separately, with the key being used as
-
# the name of the placeholder to bind the value to.
-
#
-
# The block is optional. If given, it will be invoked for each row returned
-
# by the query. Otherwise, any results are accumulated into an array and
-
# returned wholesale.
-
#
-
# See also #execute2, #query, and #execute_batch for additional ways of
-
# executing statements.
-
1
def execute sql, bind_vars = [], *args, &block
-
# FIXME: This is a terrible hack and should be removed but is required
-
# for older versions of rails
-
7
hack = Object.const_defined?(:ActiveRecord) && sql =~ /^PRAGMA index_list/
-
-
7
if bind_vars.nil? || !args.empty?
-
if args.empty?
-
bind_vars = []
-
else
-
bind_vars = [bind_vars] + args
-
end
-
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling SQLite3::Database#execute with nil or multiple bind params
-
without using an array. Please switch to passing bind parameters as an array.
-
Support for bind parameters as *args will be removed in 2.0.0.
-
eowarn
-
end
-
-
7
prepare( sql ) do |stmt|
-
7
stmt.bind_params(bind_vars)
-
7
columns = stmt.columns
-
7
stmt = ResultSet.new(self, stmt).to_a if type_translation
-
-
7
if block_given?
-
stmt.each do |row|
-
if @results_as_hash
-
yield type_translation ? row : ordered_map_for(columns, row)
-
else
-
yield row
-
end
-
end
-
else
-
7
if @results_as_hash
-
7
stmt.map { |row|
-
h = type_translation ? row : ordered_map_for(columns, row)
-
-
# FIXME UGH TERRIBLE HACK!
-
h['unique'] = h['unique'].to_s if hack
-
-
h
-
}
-
else
-
stmt.to_a
-
end
-
end
-
end
-
end
-
-
# Executes the given SQL statement, exactly as with #execute. However, the
-
# first row returned (either via the block, or in the returned array) is
-
# always the names of the columns. Subsequent rows correspond to the data
-
# from the result set.
-
#
-
# Thus, even if the query itself returns no rows, this method will always
-
# return at least one row--the names of the columns.
-
#
-
# See also #execute, #query, and #execute_batch for additional ways of
-
# executing statements.
-
1
def execute2( sql, *bind_vars )
-
prepare( sql ) do |stmt|
-
result = stmt.execute( *bind_vars )
-
if block_given?
-
yield stmt.columns
-
result.each { |row| yield row }
-
else
-
return result.inject( [ stmt.columns ] ) { |arr,row|
-
arr << row; arr }
-
end
-
end
-
end
-
-
# Executes all SQL statements in the given string. By contrast, the other
-
# means of executing queries will only execute the first statement in the
-
# string, ignoring all subsequent statements. This will execute each one
-
# in turn. The same bind parameters, if given, will be applied to each
-
# statement.
-
#
-
# This always returns +nil+, making it unsuitable for queries that return
-
# rows.
-
1
def execute_batch( sql, bind_vars = [], *args )
-
# FIXME: remove this stuff later
-
unless [Array, Hash].include?(bind_vars.class)
-
bind_vars = [bind_vars]
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling SQLite3::Database#execute_batch with bind parameters
-
that are not a list of a hash. Please switch to passing bind parameters as an
-
array or hash. Support for this behavior will be removed in version 2.0.0.
-
eowarn
-
end
-
-
# FIXME: remove this stuff later
-
if bind_vars.nil? || !args.empty?
-
if args.empty?
-
bind_vars = []
-
else
-
bind_vars = [nil] + args
-
end
-
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling SQLite3::Database#execute_batch with nil or multiple bind params
-
without using an array. Please switch to passing bind parameters as an array.
-
Support for this behavior will be removed in version 2.0.0.
-
eowarn
-
end
-
-
sql = sql.strip
-
until sql.empty? do
-
prepare( sql ) do |stmt|
-
# FIXME: this should probably use sqlite3's api for batch execution
-
# This implementation requires stepping over the results.
-
if bind_vars.length == stmt.bind_parameter_count
-
stmt.bind_params(bind_vars)
-
end
-
stmt.step
-
sql = stmt.remainder.strip
-
end
-
end
-
nil
-
end
-
-
# This is a convenience method for creating a statement, binding
-
# paramters to it, and calling execute:
-
#
-
# result = db.query( "select * from foo where a=?", [5])
-
# # is the same as
-
# result = db.prepare( "select * from foo where a=?" ).execute( 5 )
-
#
-
# You must be sure to call +close+ on the ResultSet instance that is
-
# returned, or you could have problems with locks on the table. If called
-
# with a block, +close+ will be invoked implicitly when the block
-
# terminates.
-
1
def query( sql, bind_vars = [], *args )
-
-
if bind_vars.nil? || !args.empty?
-
if args.empty?
-
bind_vars = []
-
else
-
bind_vars = [bind_vars] + args
-
end
-
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling SQLite3::Database#query with nil or multiple bind params
-
without using an array. Please switch to passing bind parameters as an array.
-
Support for this will be removed in version 2.0.0.
-
eowarn
-
end
-
-
result = prepare( sql ).execute( bind_vars )
-
if block_given?
-
begin
-
yield result
-
ensure
-
result.close
-
end
-
else
-
return result
-
end
-
end
-
-
# A convenience method for obtaining the first row of a result set, and
-
# discarding all others. It is otherwise identical to #execute.
-
#
-
# See also #get_first_value.
-
1
def get_first_row( sql, *bind_vars )
-
execute( sql, *bind_vars ).first
-
end
-
-
# A convenience method for obtaining the first value of the first row of a
-
# result set, and discarding all other values and rows. It is otherwise
-
# identical to #execute.
-
#
-
# See also #get_first_row.
-
1
def get_first_value( sql, *bind_vars )
-
execute( sql, *bind_vars ) { |row| return row[0] }
-
nil
-
end
-
-
1
alias :busy_timeout :busy_timeout=
-
-
# Creates a new function for use in SQL statements. It will be added as
-
# +name+, with the given +arity+. (For variable arity functions, use
-
# -1 for the arity.)
-
#
-
# The block should accept at least one parameter--the FunctionProxy
-
# instance that wraps this function invocation--and any other
-
# arguments it needs (up to its arity).
-
#
-
# The block does not return a value directly. Instead, it will invoke
-
# the FunctionProxy#result= method on the +func+ parameter and
-
# indicate the return value that way.
-
#
-
# Example:
-
#
-
# db.create_function( "maim", 1 ) do |func, value|
-
# if value.nil?
-
# func.result = nil
-
# else
-
# func.result = value.split(//).sort.join
-
# end
-
# end
-
#
-
# puts db.get_first_value( "select maim(name) from table" )
-
1
def create_function name, arity, text_rep=Constants::TextRep::ANY, &block
-
define_function(name) do |*args|
-
fp = FunctionProxy.new
-
block.call(fp, *args)
-
fp.result
-
end
-
self
-
end
-
-
# Creates a new aggregate function for use in SQL statements. Aggregate
-
# functions are functions that apply over every row in the result set,
-
# instead of over just a single row. (A very common aggregate function
-
# is the "count" function, for determining the number of rows that match
-
# a query.)
-
#
-
# The new function will be added as +name+, with the given +arity+. (For
-
# variable arity functions, use -1 for the arity.)
-
#
-
# The +step+ parameter must be a proc object that accepts as its first
-
# parameter a FunctionProxy instance (representing the function
-
# invocation), with any subsequent parameters (up to the function's arity).
-
# The +step+ callback will be invoked once for each row of the result set.
-
#
-
# The +finalize+ parameter must be a +proc+ object that accepts only a
-
# single parameter, the FunctionProxy instance representing the current
-
# function invocation. It should invoke FunctionProxy#result= to
-
# store the result of the function.
-
#
-
# Example:
-
#
-
# db.create_aggregate( "lengths", 1 ) do
-
# step do |func, value|
-
# func[ :total ] ||= 0
-
# func[ :total ] += ( value ? value.length : 0 )
-
# end
-
#
-
# finalize do |func|
-
# func.result = func[ :total ] || 0
-
# end
-
# end
-
#
-
# puts db.get_first_value( "select lengths(name) from table" )
-
#
-
# See also #create_aggregate_handler for a more object-oriented approach to
-
# aggregate functions.
-
1
def create_aggregate( name, arity, step=nil, finalize=nil,
-
text_rep=Constants::TextRep::ANY, &block )
-
-
factory = Class.new do
-
def self.step( &block )
-
define_method(:step, &block)
-
end
-
-
def self.finalize( &block )
-
define_method(:finalize, &block)
-
end
-
end
-
-
if block_given?
-
factory.instance_eval(&block)
-
else
-
factory.class_eval do
-
define_method(:step, step)
-
define_method(:finalize, finalize)
-
end
-
end
-
-
proxy = factory.new
-
proxy.extend(Module.new {
-
attr_accessor :ctx
-
-
def step( *args )
-
super(@ctx, *args)
-
end
-
-
def finalize
-
super(@ctx)
-
end
-
})
-
proxy.ctx = FunctionProxy.new
-
define_aggregator(name, proxy)
-
end
-
-
# This is another approach to creating an aggregate function (see
-
# #create_aggregate). Instead of explicitly specifying the name,
-
# callbacks, arity, and type, you specify a factory object
-
# (the "handler") that knows how to obtain all of that information. The
-
# handler should respond to the following messages:
-
#
-
# +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This
-
# message is optional, and if the handler does not respond to it,
-
# the function will have an arity of -1.
-
# +name+:: this is the name of the function. The handler _must_ implement
-
# this message.
-
# +new+:: this must be implemented by the handler. It should return a new
-
# instance of the object that will handle a specific invocation of
-
# the function.
-
#
-
# The handler instance (the object returned by the +new+ message, described
-
# above), must respond to the following messages:
-
#
-
# +step+:: this is the method that will be called for each step of the
-
# aggregate function's evaluation. It should implement the same
-
# signature as the +step+ callback for #create_aggregate.
-
# +finalize+:: this is the method that will be called to finalize the
-
# aggregate function's evaluation. It should implement the
-
# same signature as the +finalize+ callback for
-
# #create_aggregate.
-
#
-
# Example:
-
#
-
# class LengthsAggregateHandler
-
# def self.arity; 1; end
-
#
-
# def initialize
-
# @total = 0
-
# end
-
#
-
# def step( ctx, name )
-
# @total += ( name ? name.length : 0 )
-
# end
-
#
-
# def finalize( ctx )
-
# ctx.result = @total
-
# end
-
# end
-
#
-
# db.create_aggregate_handler( LengthsAggregateHandler )
-
# puts db.get_first_value( "select lengths(name) from A" )
-
1
def create_aggregate_handler( handler )
-
proxy = Class.new do
-
def initialize handler
-
@handler = handler
-
@fp = FunctionProxy.new
-
end
-
-
def step( *args )
-
@handler.step(@fp, *args)
-
end
-
-
def finalize
-
@handler.finalize @fp
-
@fp.result
-
end
-
end
-
define_aggregator(handler.name, proxy.new(handler.new))
-
self
-
end
-
-
# Begins a new transaction. Note that nested transactions are not allowed
-
# by SQLite, so attempting to nest a transaction will result in a runtime
-
# exception.
-
#
-
# The +mode+ parameter may be either <tt>:deferred</tt> (the default),
-
# <tt>:immediate</tt>, or <tt>:exclusive</tt>.
-
#
-
# If a block is given, the database instance is yielded to it, and the
-
# transaction is committed when the block terminates. If the block
-
# raises an exception, a rollback will be performed instead. Note that if
-
# a block is given, #commit and #rollback should never be called
-
# explicitly or you'll get an error when the block terminates.
-
#
-
# If a block is not given, it is the caller's responsibility to end the
-
# transaction explicitly, either by calling #commit, or by calling
-
# #rollback.
-
1
def transaction( mode = :deferred )
-
execute "begin #{mode.to_s} transaction"
-
-
if block_given?
-
abort = false
-
begin
-
yield self
-
rescue ::Object
-
abort = true
-
raise
-
ensure
-
abort and rollback or commit
-
end
-
end
-
-
true
-
end
-
-
# Commits the current transaction. If there is no current transaction,
-
# this will cause an error to be raised. This returns +true+, in order
-
# to allow it to be used in idioms like
-
# <tt>abort? and rollback or commit</tt>.
-
1
def commit
-
execute "commit transaction"
-
true
-
end
-
-
# Rolls the current transaction back. If there is no current transaction,
-
# this will cause an error to be raised. This returns +true+, in order
-
# to allow it to be used in idioms like
-
# <tt>abort? and rollback or commit</tt>.
-
1
def rollback
-
execute "rollback transaction"
-
true
-
end
-
-
# Returns +true+ if the database has been open in readonly mode
-
# A helper to check before performing any operation
-
1
def readonly?
-
@readonly
-
end
-
-
# A helper class for dealing with custom functions (see #create_function,
-
# #create_aggregate, and #create_aggregate_handler). It encapsulates the
-
# opaque function object that represents the current invocation. It also
-
# provides more convenient access to the API functions that operate on
-
# the function object.
-
#
-
# This class will almost _always_ be instantiated indirectly, by working
-
# with the create methods mentioned above.
-
1
class FunctionProxy
-
1
attr_accessor :result
-
-
# Create a new FunctionProxy that encapsulates the given +func+ object.
-
# If context is non-nil, the functions context will be set to that. If
-
# it is non-nil, it must quack like a Hash. If it is nil, then none of
-
# the context functions will be available.
-
1
def initialize
-
@result = nil
-
@context = {}
-
end
-
-
# Set the result of the function to the given error message.
-
# The function will then return that error.
-
1
def set_error( error )
-
@driver.result_error( @func, error.to_s, -1 )
-
end
-
-
# (Only available to aggregate functions.) Returns the number of rows
-
# that the aggregate has processed so far. This will include the current
-
# row, and so will always return at least 1.
-
1
def count
-
@driver.aggregate_count( @func )
-
end
-
-
# Returns the value with the given key from the context. This is only
-
# available to aggregate functions.
-
1
def []( key )
-
@context[ key ]
-
end
-
-
# Sets the value with the given key in the context. This is only
-
# available to aggregate functions.
-
1
def []=( key, value )
-
@context[ key ] = value
-
end
-
end
-
-
1
private
-
-
1
def ordered_map_for columns, row
-
h = Hash[*columns.zip(row).flatten]
-
row.each_with_index { |r, i| h[i] = r }
-
h
-
end
-
end
-
end
-
1
require 'sqlite3/constants'
-
-
1
module SQLite3
-
1
class Exception < ::StandardError
-
1
@code = 0
-
-
# The numeric error code that this exception represents.
-
1
def self.code
-
@code
-
end
-
-
# A convenience for accessing the error code for this exception.
-
1
def code
-
self.class.code
-
end
-
end
-
-
1
class SQLException < Exception; end
-
1
class InternalException < Exception; end
-
1
class PermissionException < Exception; end
-
1
class AbortException < Exception; end
-
1
class BusyException < Exception; end
-
1
class LockedException < Exception; end
-
1
class MemoryException < Exception; end
-
1
class ReadOnlyException < Exception; end
-
1
class InterruptException < Exception; end
-
1
class IOException < Exception; end
-
1
class CorruptException < Exception; end
-
1
class NotFoundException < Exception; end
-
1
class FullException < Exception; end
-
1
class CantOpenException < Exception; end
-
1
class ProtocolException < Exception; end
-
1
class EmptyException < Exception; end
-
1
class SchemaChangedException < Exception; end
-
1
class TooBigException < Exception; end
-
1
class ConstraintException < Exception; end
-
1
class MismatchException < Exception; end
-
1
class MisuseException < Exception; end
-
1
class UnsupportedException < Exception; end
-
1
class AuthorizationException < Exception; end
-
1
class FormatException < Exception; end
-
1
class RangeException < Exception; end
-
1
class NotADatabaseException < Exception; end
-
end
-
1
require 'sqlite3/errors'
-
-
1
module SQLite3
-
-
# This module is intended for inclusion solely by the Database class. It
-
# defines convenience methods for the various pragmas supported by SQLite3.
-
#
-
# For a detailed description of these pragmas, see the SQLite3 documentation
-
# at http://sqlite.org/pragma.html.
-
1
module Pragmas
-
-
# Returns +true+ or +false+ depending on the value of the named pragma.
-
1
def get_boolean_pragma( name )
-
get_first_value( "PRAGMA #{name}" ) != "0"
-
end
-
1
private :get_boolean_pragma
-
-
# Sets the given pragma to the given boolean value. The value itself
-
# may be +true+ or +false+, or any other commonly used string or
-
# integer that represents truth.
-
1
def set_boolean_pragma( name, mode )
-
case mode
-
when String
-
case mode.downcase
-
when "on", "yes", "true", "y", "t"; mode = "'ON'"
-
when "off", "no", "false", "n", "f"; mode = "'OFF'"
-
else
-
raise Exception,
-
"unrecognized pragma parameter #{mode.inspect}"
-
end
-
when true, 1
-
mode = "ON"
-
when false, 0, nil
-
mode = "OFF"
-
else
-
raise Exception,
-
"unrecognized pragma parameter #{mode.inspect}"
-
end
-
-
execute( "PRAGMA #{name}=#{mode}" )
-
end
-
1
private :set_boolean_pragma
-
-
# Requests the given pragma (and parameters), and if the block is given,
-
# each row of the result set will be yielded to it. Otherwise, the results
-
# are returned as an array.
-
1
def get_query_pragma( name, *parms, &block ) # :yields: row
-
if parms.empty?
-
execute( "PRAGMA #{name}", &block )
-
else
-
args = "'" + parms.join("','") + "'"
-
execute( "PRAGMA #{name}( #{args} )", &block )
-
end
-
end
-
1
private :get_query_pragma
-
-
# Return the value of the given pragma.
-
1
def get_enum_pragma( name )
-
get_first_value( "PRAGMA #{name}" )
-
end
-
1
private :get_enum_pragma
-
-
# Set the value of the given pragma to +mode+. The +mode+ parameter must
-
# conform to one of the values in the given +enum+ array. Each entry in
-
# the array is another array comprised of elements in the enumeration that
-
# have duplicate values. See #synchronous, #default_synchronous,
-
# #temp_store, and #default_temp_store for usage examples.
-
1
def set_enum_pragma( name, mode, enums )
-
match = enums.find { |p| p.find { |i| i.to_s.downcase == mode.to_s.downcase } }
-
raise Exception,
-
"unrecognized #{name} #{mode.inspect}" unless match
-
execute( "PRAGMA #{name}='#{match.first.upcase}'" )
-
end
-
1
private :set_enum_pragma
-
-
# Returns the value of the given pragma as an integer.
-
1
def get_int_pragma( name )
-
get_first_value( "PRAGMA #{name}" ).to_i
-
end
-
1
private :get_int_pragma
-
-
# Set the value of the given pragma to the integer value of the +value+
-
# parameter.
-
1
def set_int_pragma( name, value )
-
execute( "PRAGMA #{name}=#{value.to_i}" )
-
end
-
1
private :set_int_pragma
-
-
# The enumeration of valid synchronous modes.
-
1
SYNCHRONOUS_MODES = [ [ 'full', 2 ], [ 'normal', 1 ], [ 'off', 0 ] ]
-
-
# The enumeration of valid temp store modes.
-
1
TEMP_STORE_MODES = [ [ 'default', 0 ], [ 'file', 1 ], [ 'memory', 2 ] ]
-
-
# Does an integrity check on the database. If the check fails, a
-
# SQLite3::Exception will be raised. Otherwise it
-
# returns silently.
-
1
def integrity_check
-
execute( "PRAGMA integrity_check" ) do |row|
-
raise Exception, row[0] if row[0] != "ok"
-
end
-
end
-
-
1
def auto_vacuum
-
get_boolean_pragma "auto_vacuum"
-
end
-
-
1
def auto_vacuum=( mode )
-
set_boolean_pragma "auto_vacuum", mode
-
end
-
-
1
def schema_cookie
-
get_int_pragma "schema_cookie"
-
end
-
-
1
def schema_cookie=( cookie )
-
set_int_pragma "schema_cookie", cookie
-
end
-
-
1
def user_cookie
-
get_int_pragma "user_cookie"
-
end
-
-
1
def user_cookie=( cookie )
-
set_int_pragma "user_cookie", cookie
-
end
-
-
1
def cache_size
-
get_int_pragma "cache_size"
-
end
-
-
1
def cache_size=( size )
-
set_int_pragma "cache_size", size
-
end
-
-
1
def default_cache_size
-
get_int_pragma "default_cache_size"
-
end
-
-
1
def default_cache_size=( size )
-
set_int_pragma "default_cache_size", size
-
end
-
-
1
def default_synchronous
-
get_enum_pragma "default_synchronous"
-
end
-
-
1
def default_synchronous=( mode )
-
set_enum_pragma "default_synchronous", mode, SYNCHRONOUS_MODES
-
end
-
-
1
def synchronous
-
get_enum_pragma "synchronous"
-
end
-
-
1
def synchronous=( mode )
-
set_enum_pragma "synchronous", mode, SYNCHRONOUS_MODES
-
end
-
-
1
def default_temp_store
-
get_enum_pragma "default_temp_store"
-
end
-
-
1
def default_temp_store=( mode )
-
set_enum_pragma "default_temp_store", mode, TEMP_STORE_MODES
-
end
-
-
1
def temp_store
-
get_enum_pragma "temp_store"
-
end
-
-
1
def temp_store=( mode )
-
set_enum_pragma "temp_store", mode, TEMP_STORE_MODES
-
end
-
-
1
def full_column_names
-
get_boolean_pragma "full_column_names"
-
end
-
-
1
def full_column_names=( mode )
-
set_boolean_pragma "full_column_names", mode
-
end
-
-
1
def parser_trace
-
get_boolean_pragma "parser_trace"
-
end
-
-
1
def parser_trace=( mode )
-
set_boolean_pragma "parser_trace", mode
-
end
-
-
1
def vdbe_trace
-
get_boolean_pragma "vdbe_trace"
-
end
-
-
1
def vdbe_trace=( mode )
-
set_boolean_pragma "vdbe_trace", mode
-
end
-
-
1
def database_list( &block ) # :yields: row
-
get_query_pragma "database_list", &block
-
end
-
-
1
def foreign_key_list( table, &block ) # :yields: row
-
get_query_pragma "foreign_key_list", table, &block
-
end
-
-
1
def index_info( index, &block ) # :yields: row
-
get_query_pragma "index_info", index, &block
-
end
-
-
1
def index_list( table, &block ) # :yields: row
-
get_query_pragma "index_list", table, &block
-
end
-
-
###
-
# Returns information about +table+. Yields each row of table information
-
# if a block is provided.
-
1
def table_info table
-
stmt = prepare "PRAGMA table_info(#{table})"
-
columns = stmt.columns
-
-
needs_tweak_default =
-
version_compare(SQLite3.libversion.to_s, "3.3.7") > 0
-
-
result = [] unless block_given?
-
stmt.each do |row|
-
new_row = Hash[columns.zip(row)]
-
-
# FIXME: This should be removed but is required for older versions
-
# of rails
-
if(Object.const_defined?(:ActiveRecord))
-
new_row['notnull'] = new_row['notnull'].to_s
-
end
-
-
tweak_default(new_row) if needs_tweak_default
-
-
if block_given?
-
yield new_row
-
else
-
result << new_row
-
end
-
end
-
stmt.close
-
-
result
-
end
-
-
1
private
-
-
# Compares two version strings
-
1
def version_compare(v1, v2)
-
v1 = v1.split(".").map { |i| i.to_i }
-
v2 = v2.split(".").map { |i| i.to_i }
-
parts = [v1.length, v2.length].max
-
v1.push 0 while v1.length < parts
-
v2.push 0 while v2.length < parts
-
v1.zip(v2).each do |a,b|
-
return -1 if a < b
-
return 1 if a > b
-
end
-
return 0
-
end
-
-
# Since SQLite 3.3.8, the table_info pragma has returned the default
-
# value of the row as a quoted SQL value. This method essentially
-
# unquotes those values.
-
1
def tweak_default(hash)
-
case hash["dflt_value"]
-
when /^null$/i
-
hash["dflt_value"] = nil
-
when /^'(.*)'$/
-
hash["dflt_value"] = $1.gsub(/''/, "'")
-
when /^"(.*)"$/
-
hash["dflt_value"] = $1.gsub(/""/, '"')
-
end
-
end
-
end
-
-
end
-
1
require 'sqlite3/constants'
-
1
require 'sqlite3/errors'
-
-
1
module SQLite3
-
-
# The ResultSet object encapsulates the enumerability of a query's output.
-
# It is a simple cursor over the data that the query returns. It will
-
# very rarely (if ever) be instantiated directly. Instead, client's should
-
# obtain a ResultSet instance via Statement#execute.
-
1
class ResultSet
-
1
include Enumerable
-
-
1
class ArrayWithTypes < Array # :nodoc:
-
1
attr_accessor :types
-
end
-
-
1
class ArrayWithTypesAndFields < Array # :nodoc:
-
1
attr_writer :types
-
1
attr_writer :fields
-
-
1
def types
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling #{self.class}#types. This method will be removed in
-
sqlite3 version 2.0.0, please call the `types` method on the SQLite3::ResultSet
-
object that created this object
-
eowarn
-
@types
-
end
-
-
1
def fields
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling #{self.class}#fields. This method will be removed in
-
sqlite3 version 2.0.0, please call the `columns` method on the SQLite3::ResultSet
-
object that created this object
-
eowarn
-
@fields
-
end
-
end
-
-
# The class of which we return an object in case we want a Hash as
-
# result.
-
1
class HashWithTypesAndFields < Hash # :nodoc:
-
1
attr_writer :types
-
1
attr_writer :fields
-
-
1
def types
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling #{self.class}#types. This method will be removed in
-
sqlite3 version 2.0.0, please call the `types` method on the SQLite3::ResultSet
-
object that created this object
-
eowarn
-
@types
-
end
-
-
1
def fields
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling #{self.class}#fields. This method will be removed in
-
sqlite3 version 2.0.0, please call the `columns` method on the SQLite3::ResultSet
-
object that created this object
-
eowarn
-
@fields
-
end
-
-
1
def [] key
-
key = fields[key] if key.is_a? Numeric
-
super key
-
end
-
end
-
-
# Create a new ResultSet attached to the given database, using the
-
# given sql text.
-
1
def initialize db, stmt
-
@db = db
-
@stmt = stmt
-
end
-
-
# Reset the cursor, so that a result set which has reached end-of-file
-
# can be rewound and reiterated.
-
1
def reset( *bind_params )
-
@stmt.reset!
-
@stmt.bind_params( *bind_params )
-
@eof = false
-
end
-
-
# Query whether the cursor has reached the end of the result set or not.
-
1
def eof?
-
@stmt.done?
-
end
-
-
# Obtain the next row from the cursor. If there are no more rows to be
-
# had, this will return +nil+. If type translation is active on the
-
# corresponding database, the values in the row will be translated
-
# according to their types.
-
#
-
# The returned value will be an array, unless Database#results_as_hash has
-
# been set to +true+, in which case the returned value will be a hash.
-
#
-
# For arrays, the column names are accessible via the +fields+ property,
-
# and the column types are accessible via the +types+ property.
-
#
-
# For hashes, the column names are the keys of the hash, and the column
-
# types are accessible via the +types+ property.
-
1
def next
-
if @db.results_as_hash
-
return next_hash
-
end
-
-
row = @stmt.step
-
return nil if @stmt.done?
-
-
if @db.type_translation
-
row = @stmt.types.zip(row).map do |type, value|
-
@db.translator.translate( type, value )
-
end
-
end
-
-
if row.respond_to?(:fields)
-
# FIXME: this can only happen if the translator returns something
-
# that responds to `fields`. Since we're removing the translator
-
# in 2.0, we can remove this branch in 2.0.
-
row = ArrayWithTypes.new(row)
-
else
-
# FIXME: the `fields` and `types` methods are deprecated on this
-
# object for version 2.0, so we can safely remove this branch
-
# as well.
-
row = ArrayWithTypesAndFields.new(row)
-
end
-
-
row.fields = @stmt.columns
-
row.types = @stmt.types
-
row
-
end
-
-
# Required by the Enumerable mixin. Provides an internal iterator over the
-
# rows of the result set.
-
1
def each
-
while node = self.next
-
yield node
-
end
-
end
-
-
# Provides an internal iterator over the rows of the result set where
-
# each row is yielded as a hash.
-
1
def each_hash
-
while node = next_hash
-
yield node
-
end
-
end
-
-
# Closes the statement that spawned this result set.
-
# <em>Use with caution!</em> Closing a result set will automatically
-
# close any other result sets that were spawned from the same statement.
-
1
def close
-
@stmt.close
-
end
-
-
# Queries whether the underlying statement has been closed or not.
-
1
def closed?
-
@stmt.closed?
-
end
-
-
# Returns the types of the columns returned by this result set.
-
1
def types
-
@stmt.types
-
end
-
-
# Returns the names of the columns returned by this result set.
-
1
def columns
-
@stmt.columns
-
end
-
-
# Return the next row as a hash
-
1
def next_hash
-
row = @stmt.step
-
return nil if @stmt.done?
-
-
# FIXME: type translation is deprecated, so this can be removed
-
# in 2.0
-
if @db.type_translation
-
row = @stmt.types.zip(row).map do |type, value|
-
@db.translator.translate( type, value )
-
end
-
end
-
-
# FIXME: this can be switched to a regular hash in 2.0
-
row = HashWithTypesAndFields[*@stmt.columns.zip(row).flatten]
-
-
# FIXME: these methods are deprecated for version 2.0, so we can remove
-
# this code in 2.0
-
row.fields = @stmt.columns
-
row.types = @stmt.types
-
row
-
end
-
end
-
end
-
1
require 'sqlite3/errors'
-
1
require 'sqlite3/resultset'
-
-
1
class String
-
1
def to_blob
-
SQLite3::Blob.new( self )
-
end
-
end
-
-
1
module SQLite3
-
# A statement represents a prepared-but-unexecuted SQL query. It will rarely
-
# (if ever) be instantiated directly by a client, and is most often obtained
-
# via the Database#prepare method.
-
1
class Statement
-
1
include Enumerable
-
-
# This is any text that followed the first valid SQL statement in the text
-
# with which the statement was initialized. If there was no trailing text,
-
# this will be the empty string.
-
1
attr_reader :remainder
-
-
# Binds the given variables to the corresponding placeholders in the SQL
-
# text.
-
#
-
# See Database#execute for a description of the valid placeholder
-
# syntaxes.
-
#
-
# Example:
-
#
-
# stmt = db.prepare( "select * from table where a=? and b=?" )
-
# stmt.bind_params( 15, "hello" )
-
#
-
# See also #execute, #bind_param, Statement#bind_param, and
-
# Statement#bind_params.
-
1
def bind_params( *bind_vars )
-
7
index = 1
-
7
bind_vars.flatten.each do |var|
-
if Hash === var
-
var.each { |key, val| bind_param key, val }
-
else
-
bind_param index, var
-
index += 1
-
end
-
end
-
end
-
-
# Execute the statement. This creates a new ResultSet object for the
-
# statement's virtual machine. If a block was given, the new ResultSet will
-
# be yielded to it; otherwise, the ResultSet will be returned.
-
#
-
# Any parameters will be bound to the statement using #bind_params.
-
#
-
# Example:
-
#
-
# stmt = db.prepare( "select * from table" )
-
# stmt.execute do |result|
-
# ...
-
# end
-
#
-
# See also #bind_params, #execute!.
-
1
def execute( *bind_vars )
-
reset! if active? || done?
-
-
bind_params(*bind_vars) unless bind_vars.empty?
-
@results = ResultSet.new(@connection, self)
-
-
step if 0 == column_count
-
-
yield @results if block_given?
-
@results
-
end
-
-
# Execute the statement. If no block was given, this returns an array of
-
# rows returned by executing the statement. Otherwise, each row will be
-
# yielded to the block.
-
#
-
# Any parameters will be bound to the statement using #bind_params.
-
#
-
# Example:
-
#
-
# stmt = db.prepare( "select * from table" )
-
# stmt.execute! do |row|
-
# ...
-
# end
-
#
-
# See also #bind_params, #execute.
-
1
def execute!( *bind_vars, &block )
-
execute(*bind_vars)
-
block_given? ? each(&block) : to_a
-
end
-
-
# Returns true if the statement is currently active, meaning it has an
-
# open result set.
-
1
def active?
-
!done?
-
end
-
-
# Return an array of the column names for this statement. Note that this
-
# may execute the statement in order to obtain the metadata; this makes it
-
# a (potentially) expensive operation.
-
1
def columns
-
7
get_metadata unless @columns
-
7
return @columns
-
end
-
-
1
def each
-
7
loop do
-
7
val = step
-
7
break self if done?
-
yield val
-
end
-
end
-
-
# Return an array of the data types for each column in this statement. Note
-
# that this may execute the statement in order to obtain the metadata; this
-
# makes it a (potentially) expensive operation.
-
1
def types
-
must_be_open!
-
get_metadata unless @types
-
@types
-
end
-
-
# Performs a sanity check to ensure that the statement is not
-
# closed. If it is, an exception is raised.
-
1
def must_be_open! # :nodoc:
-
if closed?
-
raise SQLite3::Exception, "cannot use a closed statement"
-
end
-
end
-
-
1
private
-
# A convenience method for obtaining the metadata about the query. Note
-
# that this will actually execute the SQL, which means it can be a
-
# (potentially) expensive operation.
-
1
def get_metadata
-
7
@columns = Array.new(column_count) do |column|
-
column_name column
-
end
-
7
@types = Array.new(column_count) do |column|
-
column_decltype column
-
end
-
end
-
end
-
end
-
1
require 'time'
-
1
require 'date'
-
-
1
module SQLite3
-
-
# The Translator class encapsulates the logic and callbacks necessary for
-
# converting string data to a value of some specified type. Every Database
-
# instance may have a Translator instance, in order to assist in type
-
# translation (Database#type_translation).
-
#
-
# Further, applications may define their own custom type translation logic
-
# by registering translator blocks with the corresponding database's
-
# translator instance (Database#translator).
-
1
class Translator
-
-
# Create a new Translator instance. It will be preinitialized with default
-
# translators for most SQL data types.
-
1
def initialize
-
@translators = Hash.new( proc { |type,value| value } )
-
@type_name_cache = {}
-
register_default_translators
-
end
-
-
# Add a new translator block, which will be invoked to process type
-
# translations to the given type. The type should be an SQL datatype, and
-
# may include parentheses (i.e., "VARCHAR(30)"). However, any parenthetical
-
# information is stripped off and discarded, so type translation decisions
-
# are made solely on the "base" type name.
-
#
-
# The translator block itself should accept two parameters, "type" and
-
# "value". In this case, the "type" is the full type name (including
-
# parentheses), so the block itself may include logic for changing how a
-
# type is translated based on the additional data. The "value" parameter
-
# is the (string) data to convert.
-
#
-
# The block should return the translated value.
-
1
def add_translator( type, &block ) # :yields: type, value
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]} is calling `add_translator`.
-
Built in translators are deprecated and will be removed in version 2.0.0
-
eowarn
-
@translators[ type_name( type ) ] = block
-
end
-
-
# Translate the given string value to a value of the given type. In the
-
# absense of an installed translator block for the given type, the value
-
# itself is always returned. Further, +nil+ values are never translated,
-
# and are always passed straight through regardless of the type parameter.
-
1
def translate( type, value )
-
unless value.nil?
-
# FIXME: this is a hack to support Sequel
-
if type && %w{ datetime timestamp }.include?(type.downcase)
-
@translators[ type_name( type ) ].call( type, value.to_s )
-
else
-
@translators[ type_name( type ) ].call( type, value )
-
end
-
end
-
end
-
-
# A convenience method for working with type names. This returns the "base"
-
# type name, without any parenthetical data.
-
1
def type_name( type )
-
@type_name_cache[type] ||= begin
-
type = "" if type.nil?
-
type = $1 if type =~ /^(.*?)\(/
-
type.upcase
-
end
-
end
-
1
private :type_name
-
-
# Register the default translators for the current Translator instance.
-
# This includes translators for most major SQL data types.
-
1
def register_default_translators
-
[ "time",
-
"timestamp" ].each { |type| add_translator( type ) { |t, v| Time.parse( v ) } }
-
-
add_translator( "date" ) { |t,v| Date.parse(v) }
-
add_translator( "datetime" ) { |t,v| DateTime.parse(v) }
-
-
[ "decimal",
-
"float",
-
"numeric",
-
"double",
-
"real",
-
"dec",
-
"fixed" ].each { |type| add_translator( type ) { |t,v| v.to_f } }
-
-
[ "integer",
-
"smallint",
-
"mediumint",
-
"int",
-
"bigint" ].each { |type| add_translator( type ) { |t,v| v.to_i } }
-
-
[ "bit",
-
"bool",
-
"boolean" ].each do |type|
-
add_translator( type ) do |t,v|
-
!( v.strip.gsub(/00+/,"0") == "0" ||
-
v.downcase == "false" ||
-
v.downcase == "f" ||
-
v.downcase == "no" ||
-
v.downcase == "n" )
-
end
-
end
-
-
add_translator( "tinyint" ) do |type, value|
-
if type =~ /\(\s*1\s*\)/
-
value.to_i == 1
-
else
-
value.to_i
-
end
-
end
-
end
-
1
private :register_default_translators
-
-
end
-
-
end
-
1
require 'sqlite3/constants'
-
-
1
module SQLite3
-
-
1
class Value
-
1
attr_reader :handle
-
-
1
def initialize( db, handle )
-
@driver = db.driver
-
@handle = handle
-
end
-
-
1
def null?
-
type == :null
-
end
-
-
1
def to_blob
-
@driver.value_blob( @handle )
-
end
-
-
1
def length( utf16=false )
-
if utf16
-
@driver.value_bytes16( @handle )
-
else
-
@driver.value_bytes( @handle )
-
end
-
end
-
-
1
def to_f
-
@driver.value_double( @handle )
-
end
-
-
1
def to_i
-
@driver.value_int( @handle )
-
end
-
-
1
def to_int64
-
@driver.value_int64( @handle )
-
end
-
-
1
def to_s( utf16=false )
-
@driver.value_text( @handle, utf16 )
-
end
-
-
1
def type
-
case @driver.value_type( @handle )
-
when Constants::ColumnType::INTEGER then :int
-
when Constants::ColumnType::FLOAT then :float
-
when Constants::ColumnType::TEXT then :text
-
when Constants::ColumnType::BLOB then :blob
-
when Constants::ColumnType::NULL then :null
-
end
-
end
-
-
end
-
-
end
-
1
module SQLite3
-
-
1
VERSION = '1.3.6'
-
-
1
module VersionProxy
-
-
1
MAJOR = 1
-
1
MINOR = 3
-
1
TINY = 6
-
1
BUILD = nil
-
-
1
STRING = [ MAJOR, MINOR, TINY, BUILD ].compact.join( "." )
-
#:beta-tag:
-
-
1
VERSION = ::SQLite3::VERSION
-
end
-
-
1
def self.const_missing(name)
-
return super unless name == :Version
-
warn(<<-eowarn) if $VERBOSE
-
#{caller[0]}: SQLite::Version will be removed in sqlite3-ruby version 2.0.0
-
eowarn
-
VersionProxy
-
end
-
end
-
1
require 'treetop/ruby_extensions/string'
-
1
class String
-
1
def column_of(index)
-
return 1 if index == 0
-
newline_index = rindex("\n", index - 1)
-
if newline_index
-
index - newline_index
-
else
-
index + 1
-
end
-
end
-
-
1
def line_of(index)
-
self[0...index].count("\n") + 1
-
end
-
-
1
unless method_defined?(:blank?)
-
def blank?
-
self == ""
-
end
-
end
-
-
# The following methods are lifted from Facets 2.0.2
-
1
def tabto(n)
-
if self =~ /^( *)\S/
-
indent(n - $1.length)
-
else
-
self
-
end
-
end
-
-
1
def indent(n)
-
if n >= 0
-
gsub(/^/, ' ' * n)
-
else
-
gsub(/^ {0,#{-n}}/, "")
-
end
-
end
-
-
1
def treetop_camelize
-
to_s.gsub(/\/(.?)/){ "::" + $1.upcase }.gsub(/(^|_)(.)/){ $2.upcase }
-
end
-
end
-
1
require 'treetop/ruby_extensions'
-
-
1
require 'treetop/runtime/compiled_parser'
-
1
require 'treetop/runtime/syntax_node'
-
1
require 'treetop/runtime/terminal_parse_failure'
-
1
require 'treetop/runtime/interval_skip_list'
-
1
module Treetop
-
1
module Runtime
-
1
class CompiledParser
-
1
include Treetop::Runtime
-
-
1
attr_reader :input, :index, :max_terminal_failure_index
-
1
attr_writer :root
-
1
attr_accessor :consume_all_input
-
1
alias :consume_all_input? :consume_all_input
-
-
1
def initialize
-
24
self.consume_all_input = true
-
end
-
-
1
def parse(input, options = {})
-
24
prepare_to_parse(input)
-
24
@index = options[:index] if options[:index]
-
24
result = send("_nt_#{options[:root] || root}")
-
24
should_consume_all = options.include?(:consume_all_input) ? options[:consume_all_input] : consume_all_input?
-
24
return nil if (should_consume_all && index != input.size)
-
24
return SyntaxNode.new(input, index...(index + 1)) if result == true
-
24
return result
-
end
-
-
1
def failure_index
-
max_terminal_failure_index
-
end
-
-
1
def failure_line
-
@terminal_failures && input.line_of(failure_index)
-
end
-
-
1
def failure_column
-
@terminal_failures && input.column_of(failure_index)
-
end
-
-
1
def failure_reason
-
return nil unless (tf = terminal_failures) && tf.size > 0
-
"Expected " +
-
(tf.size == 1 ?
-
tf[0].expected_string :
-
"one of #{tf.map{|f| f.expected_string}.uniq*', '}"
-
) +
-
" at line #{failure_line}, column #{failure_column} (byte #{failure_index+1})" +
-
" after #{input[index...failure_index]}"
-
end
-
-
1
def terminal_failures
-
if @terminal_failures.empty? || @terminal_failures[0].is_a?(TerminalParseFailure)
-
@terminal_failures
-
else
-
@terminal_failures.map! {|tf_ary| TerminalParseFailure.new(*tf_ary) }
-
end
-
end
-
-
-
1
protected
-
-
1
attr_reader :node_cache, :input_length
-
1
attr_writer :index
-
-
1
def prepare_to_parse(input)
-
24
@input = input
-
24
@input_length = input.length
-
24
reset_index
-
432
@node_cache = Hash.new {|hash, key| hash[key] = Hash.new}
-
24
@regexps = {}
-
24
@terminal_failures = []
-
24
@max_terminal_failure_index = 0
-
end
-
-
1
def reset_index
-
24
@index = 0
-
end
-
-
1
def parse_anything(node_class = SyntaxNode, inline_module = nil)
-
if index < input.length
-
result = instantiate_node(node_class,input, index...(index + 1))
-
result.extend(inline_module) if inline_module
-
@index += 1
-
result
-
else
-
terminal_parse_failure("any character")
-
end
-
end
-
-
1
def instantiate_node(node_type,*args)
-
1010
if node_type.respond_to? :new
-
1010
node_type.new(*args)
-
else
-
SyntaxNode.new(*args).extend(node_type)
-
end
-
end
-
-
1
def has_terminal?(terminal, regex, index)
-
2290
if regex
-
1229
rx = @regexps[terminal] ||= Regexp.new(terminal)
-
1229
input.index(rx, index) == index
-
else
-
1061
input[index, terminal.size] == terminal
-
end
-
end
-
-
1
def terminal_parse_failure(expected_string)
-
995
return nil if index < max_terminal_failure_index
-
977
if index > max_terminal_failure_index
-
144
@max_terminal_failure_index = index
-
144
@terminal_failures = []
-
end
-
977
@terminal_failures << [index, expected_string]
-
return nil
-
end
-
end
-
end
-
end
-
1
require 'treetop/runtime/interval_skip_list/interval_skip_list'
-
1
require 'treetop/runtime/interval_skip_list/head_node'
-
1
require 'treetop/runtime/interval_skip_list/node'
-
1
class IntervalSkipList
-
1
class HeadNode
-
1
attr_reader :height, :forward, :forward_markers
-
-
1
def initialize(height)
-
@height = height
-
@forward = Array.new(height, nil)
-
@forward_markers = Array.new(height) {|i| []}
-
end
-
-
1
def top_level
-
height - 1
-
end
-
end
-
end
-
1
class IntervalSkipList
-
1
attr_reader :probability
-
-
1
def initialize
-
@head = HeadNode.new(max_height)
-
@ranges = {}
-
@probability = 0.5
-
end
-
-
1
def max_height
-
3
-
end
-
-
1
def empty?
-
head.forward[0].nil?
-
end
-
-
1
def expire(range, length_change)
-
expired_markers, first_node_after_range = overlapping(range)
-
expired_markers.each { |marker| delete(marker) }
-
first_node_after_range.propagate_length_change(length_change)
-
end
-
-
1
def overlapping(range)
-
markers, first_node = containing_with_node(range.first)
-
-
cur_node = first_node
-
begin
-
markers.concat(cur_node.forward_markers.flatten)
-
cur_node = cur_node.forward[0]
-
end while cur_node.key < range.last
-
-
return markers.uniq, cur_node
-
end
-
-
1
def containing(n)
-
containing_with_node(n).first
-
end
-
-
1
def insert(range, marker)
-
ranges[marker] = range
-
first_node = insert_node(range.first)
-
first_node.endpoint_of.push(marker)
-
last_node = insert_node(range.last)
-
last_node.endpoint_of.push(marker)
-
-
cur_node = first_node
-
cur_level = first_node.top_level
-
while next_node_at_level_inside_range?(cur_node, cur_level, range)
-
while can_ascend_from?(cur_node, cur_level) && next_node_at_level_inside_range?(cur_node, cur_level + 1, range)
-
cur_level += 1
-
end
-
cur_node = mark_forward_path_at_level(cur_node, cur_level, marker)
-
end
-
-
while node_inside_range?(cur_node, range)
-
while can_descend_from?(cur_level) && next_node_at_level_outside_range?(cur_node, cur_level, range)
-
cur_level -= 1
-
end
-
cur_node = mark_forward_path_at_level(cur_node, cur_level, marker)
-
end
-
end
-
-
1
def delete(marker)
-
range = ranges[marker]
-
path_to_first_node = make_path
-
first_node = find(range.first, path_to_first_node)
-
-
cur_node = first_node
-
cur_level = first_node.top_level
-
while next_node_at_level_inside_range?(cur_node, cur_level, range)
-
while can_ascend_from?(cur_node, cur_level) && next_node_at_level_inside_range?(cur_node, cur_level + 1, range)
-
cur_level += 1
-
end
-
cur_node = unmark_forward_path_at_level(cur_node, cur_level, marker)
-
end
-
-
while node_inside_range?(cur_node, range)
-
while can_descend_from?(cur_level) && next_node_at_level_outside_range?(cur_node, cur_level, range)
-
cur_level -= 1
-
end
-
cur_node = unmark_forward_path_at_level(cur_node, cur_level, marker)
-
end
-
last_node = cur_node
-
-
first_node.endpoint_of.delete(marker)
-
if first_node.endpoint_of.empty?
-
first_node.delete(path_to_first_node)
-
end
-
-
last_node.endpoint_of.delete(marker)
-
if last_node.endpoint_of.empty?
-
path_to_last_node = make_path
-
find(range.last, path_to_last_node)
-
last_node.delete(path_to_last_node)
-
end
-
end
-
-
1
protected
-
1
attr_reader :head, :ranges
-
-
1
def insert_node(key)
-
path = make_path
-
found_node = find(key, path)
-
if found_node && found_node.key == key
-
return found_node
-
else
-
return Node.new(key, next_node_height, path)
-
end
-
end
-
-
1
def containing_with_node(n)
-
containing = []
-
cur_node = head
-
(max_height - 1).downto(0) do |cur_level|
-
while (next_node = cur_node.forward[cur_level]) && next_node.key <= n
-
cur_node = next_node
-
if cur_node.key == n
-
return containing + (cur_node.markers - cur_node.endpoint_of), cur_node
-
end
-
end
-
containing.concat(cur_node.forward_markers[cur_level])
-
end
-
-
return containing, cur_node
-
end
-
-
1
def delete_node(key)
-
path = make_path
-
found_node = find(key, path)
-
found_node.delete(path) if found_node.key == key
-
end
-
-
1
def find(key, path)
-
cur_node = head
-
(max_height - 1).downto(0) do |cur_level|
-
while (next_node = cur_node.forward[cur_level]) && next_node.key < key
-
cur_node = next_node
-
end
-
path[cur_level] = cur_node
-
end
-
cur_node.forward[0]
-
end
-
-
1
def make_path
-
Array.new(max_height, nil)
-
end
-
-
1
def next_node_height
-
height = 1
-
while rand < probability && height < max_height
-
height += 1
-
end
-
height
-
end
-
-
1
def can_ascend_from?(node, level)
-
level < node.top_level
-
end
-
-
1
def can_descend_from?(level)
-
level > 0
-
end
-
-
1
def node_inside_range?(node, range)
-
node.key < range.last
-
end
-
-
1
def next_node_at_level_inside_range?(node, level, range)
-
node.forward[level] && node.forward[level].key <= range.last
-
end
-
-
1
def next_node_at_level_outside_range?(node, level, range)
-
(node.forward[level].nil? || node.forward[level].key > range.last)
-
end
-
-
1
def mark_forward_path_at_level(node, level, marker)
-
node.forward_markers[level].push(marker)
-
next_node = node.forward[level]
-
next_node.markers.push(marker)
-
node = next_node
-
end
-
-
1
def unmark_forward_path_at_level(node, level, marker)
-
node.forward_markers[level].delete(marker)
-
next_node = node.forward[level]
-
next_node.markers.delete(marker)
-
node = next_node
-
end
-
-
1
def nodes
-
nodes = []
-
cur_node = head.forward[0]
-
until cur_node.nil?
-
nodes << cur_node
-
cur_node = cur_node.forward[0]
-
end
-
nodes
-
end
-
end
-
1
class IntervalSkipList
-
1
class Node < HeadNode
-
1
attr_accessor :key
-
1
attr_reader :markers, :endpoint_of
-
-
1
def initialize(key, height, path)
-
super(height)
-
@key = key
-
@markers = []
-
@endpoint_of = []
-
update_forward_pointers(path)
-
promote_markers(path)
-
end
-
-
1
def all_forward_markers
-
markers.flatten
-
end
-
-
1
def delete(path)
-
0.upto(top_level) do |i|
-
path[i].forward[i] = forward[i]
-
end
-
demote_markers(path)
-
end
-
-
1
def propagate_length_change(length_change)
-
cur_node = self
-
while cur_node do
-
cur_node.key += length_change
-
cur_node = cur_node.forward[0]
-
end
-
end
-
-
1
protected
-
-
1
def update_forward_pointers(path)
-
0.upto(top_level) do |i|
-
forward[i] = path[i].forward[i]
-
path[i].forward[i] = self
-
end
-
end
-
-
1
def promote_markers(path)
-
promoted = []
-
new_promoted = []
-
0.upto(top_level) do |i|
-
incoming_markers = path[i].forward_markers[i]
-
markers.concat(incoming_markers)
-
-
incoming_markers.each do |marker|
-
if can_be_promoted_higher?(marker, i)
-
new_promoted.push(marker)
-
forward[i].delete_marker_from_path(marker, i, forward[i+1])
-
else
-
forward_markers[i].push(marker)
-
end
-
end
-
-
promoted.each do |marker|
-
if can_be_promoted_higher?(marker, i)
-
new_promoted.push(marker)
-
forward[i].delete_marker_from_path(marker, i, forward[i+1])
-
else
-
forward_markers[i].push(marker)
-
end
-
end
-
-
promoted = new_promoted
-
new_promoted = []
-
end
-
end
-
-
-
1
def can_be_promoted_higher?(marker, level)
-
level < top_level && forward[level + 1] && forward[level + 1].markers.include?(marker)
-
end
-
-
1
def delete_marker_from_path(marker, level, terminus)
-
cur_node = self
-
until cur_node == terminus
-
cur_node.forward_markers[level].delete(marker)
-
cur_node.markers.delete(marker)
-
cur_node = cur_node.forward[level]
-
end
-
end
-
-
1
def demote_markers(path)
-
demote_inbound_markers(path)
-
demote_outbound_markers(path)
-
end
-
-
1
def demote_inbound_markers(path)
-
demoted = []
-
new_demoted = []
-
-
top_level.downto(0) do |i|
-
incoming_markers = path[i].forward_markers[i].dup
-
incoming_markers.each do |marker|
-
unless forward_node_with_marker_at_or_above_level?(marker, i)
-
path[i].forward_markers[i].delete(marker)
-
new_demoted.push(marker)
-
end
-
end
-
-
demoted.each do |marker|
-
path[i + 1].place_marker_on_inbound_path(marker, i, path[i])
-
-
if forward[i].markers.include?(marker)
-
path[i].forward_markers[i].push(marker)
-
else
-
new_demoted.push(marker)
-
end
-
end
-
-
demoted = new_demoted
-
new_demoted = []
-
end
-
end
-
-
1
def demote_outbound_markers(path)
-
demoted = []
-
new_demoted = []
-
-
top_level.downto(0) do |i|
-
forward_markers[i].each do |marker|
-
new_demoted.push(marker) unless path[i].forward_markers[i].include?(marker)
-
end
-
-
demoted.each do |marker|
-
forward[i].place_marker_on_outbound_path(marker, i, forward[i + 1])
-
new_demoted.push(marker) unless path[i].forward_markers[i].include?(marker)
-
end
-
-
demoted = new_demoted
-
new_demoted = []
-
end
-
end
-
-
1
def forward_node_with_marker_at_or_above_level?(marker, level)
-
level.upto(top_level) do |i|
-
return true if forward[i].markers.include?(marker)
-
end
-
false
-
end
-
-
1
def place_marker_on_outbound_path(marker, level, terminus)
-
cur_node = self
-
until cur_node == terminus
-
cur_node.forward_markers[level].push(marker)
-
cur_node.markers.push(marker)
-
cur_node = cur_node.forward[level]
-
end
-
end
-
-
1
def place_marker_on_inbound_path(marker, level, terminus)
-
cur_node = self
-
until cur_node == terminus
-
cur_node.forward_markers[level].push(marker)
-
cur_node = cur_node.forward[level]
-
cur_node.markers.push(marker)
-
end
-
end
-
end
-
end
-
1
module Treetop
-
1
module Runtime
-
1
class TerminalParseFailure
-
1
attr_reader :index, :expected_string
-
-
1
def initialize(index, expected_string)
-
@index = index
-
@expected_string = expected_string
-
end
-
-
1
def to_s
-
"String matching #{expected_string} expected."
-
end
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2005-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
# Add the directory containing this file to the start of the load path if it
-
# isn't there already.
-
$:.unshift(File.dirname(__FILE__)) unless
-
1
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
-
-
-
1
require 'tzinfo/ruby_core_support'
-
1
require 'tzinfo/offset_rationals'
-
1
require 'tzinfo/time_or_datetime'
-
-
1
require 'tzinfo/timezone_definition'
-
-
1
require 'tzinfo/timezone_offset_info'
-
1
require 'tzinfo/timezone_transition_info'
-
-
1
require 'tzinfo/timezone_index_definition'
-
-
1
require 'tzinfo/timezone_info'
-
1
require 'tzinfo/data_timezone_info'
-
1
require 'tzinfo/linked_timezone_info'
-
-
1
require 'tzinfo/timezone_period'
-
1
require 'tzinfo/timezone'
-
1
require 'tzinfo/info_timezone'
-
1
require 'tzinfo/data_timezone'
-
1
require 'tzinfo/linked_timezone'
-
1
require 'tzinfo/timezone_proxy'
-
-
1
require 'tzinfo/country_index_definition'
-
1
require 'tzinfo/country_info'
-
-
1
require 'tzinfo/country'
-
1
require 'tzinfo/country_timezone'
-
#--
-
# Copyright (c) 2005-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# Thrown by Country#get if the code given is not valid.
-
1
class InvalidCountryCode < StandardError
-
end
-
-
# An ISO 3166 country. Can be used to get a list of Timezones for a country.
-
# For example:
-
#
-
# us = Country.get('US')
-
# us.zone_identifiers
-
# us.zones
-
# us.zone_info
-
1
class Country
-
1
include Comparable
-
-
# Defined countries.
-
1
@@countries = {}
-
-
# Whether the countries index has been loaded yet.
-
1
@@index_loaded = false
-
-
# Gets a Country by its ISO 3166 code. Raises an InvalidCountryCode
-
# exception if it couldn't be found.
-
1
def self.get(identifier)
-
instance = @@countries[identifier]
-
-
unless instance
-
load_index
-
info = Indexes::Countries.countries[identifier]
-
raise InvalidCountryCode.new, 'Invalid identifier' unless info
-
instance = Country.new(info)
-
@@countries[identifier] = instance
-
end
-
-
instance
-
end
-
-
# If identifier is a CountryInfo object, initializes the Country instance,
-
# otherwise calls get(identifier).
-
1
def self.new(identifier)
-
if identifier.kind_of?(CountryInfo)
-
instance = super()
-
instance.send :setup, identifier
-
instance
-
else
-
get(identifier)
-
end
-
end
-
-
# Returns an Array of all the valid country codes.
-
1
def self.all_codes
-
load_index
-
Indexes::Countries.countries.keys
-
end
-
-
# Returns an Array of all the defined Countries.
-
1
def self.all
-
load_index
-
Indexes::Countries.countries.keys.collect {|code| get(code)}
-
end
-
-
# The ISO 3166 country code.
-
1
def code
-
@info.code
-
end
-
-
# The name of the country.
-
1
def name
-
@info.name
-
end
-
-
# Alias for name.
-
1
def to_s
-
name
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
"#<#{self.class}: #{@info.code}>"
-
end
-
-
# Returns a frozen array of all the zone identifiers for the country. These
-
# are in an order that
-
# (1) makes some geographical sense, and
-
# (2) puts the most populous zones first, where that does not contradict (1).
-
1
def zone_identifiers
-
@info.zone_identifiers
-
end
-
1
alias zone_names zone_identifiers
-
-
# An array of all the Timezones for this country. Returns TimezoneProxy
-
# objects to avoid the overhead of loading Timezone definitions until
-
# a conversion is actually required. The Timezones are returned in an order
-
# that
-
# (1) makes some geographical sense, and
-
# (2) puts the most populous zones first, where that does not contradict (1).
-
1
def zones
-
zone_identifiers.collect {|id|
-
Timezone.get_proxy(id)
-
}
-
end
-
-
# Returns a frozen array of all the timezones for the for the country as
-
# CountryTimezone instances (containing extra information about each zone).
-
# These are in an order that
-
# (1) makes some geographical sense, and
-
# (2) puts the most populous zones first, where that does not contradict (1).
-
1
def zone_info
-
@info.zones
-
end
-
-
# Compare two Countries based on their code. Returns -1 if c is less
-
# than self, 0 if c is equal to self and +1 if c is greater than self.
-
1
def <=>(c)
-
code <=> c.code
-
end
-
-
# Returns true if and only if the code of c is equal to the code of this
-
# Country.
-
1
def eql?(c)
-
self == c
-
end
-
-
# Returns a hash value for this Country.
-
1
def hash
-
code.hash
-
end
-
-
# Dumps this Country for marshalling.
-
1
def _dump(limit)
-
code
-
end
-
-
# Loads a marshalled Country.
-
1
def self._load(data)
-
Country.get(data)
-
end
-
-
1
private
-
# Loads in the index of countries if it hasn't already been loaded.
-
1
def self.load_index
-
unless @@index_loaded
-
require 'tzinfo/indexes/countries'
-
@@index_loaded = true
-
end
-
end
-
-
# Called by Country.new to initialize a new Country instance. The info
-
# parameter is a CountryInfo that defines the country.
-
1
def setup(info)
-
@info = info
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# The country index file includes CountryIndexDefinition which provides
-
# a country method used to define each country in the index.
-
1
module CountryIndexDefinition #:nodoc:
-
1
def self.append_features(base)
-
super
-
base.extend(ClassMethods)
-
base.instance_eval { @countries = {} }
-
end
-
-
1
module ClassMethods #:nodoc:
-
# Defines a country with an ISO 3166 country code, name and block. The
-
# block will be evaluated to obtain all the timezones for the country.
-
# Calls Country.country_defined with the definition of each country.
-
1
def country(code, name, &block)
-
@countries[code] = CountryInfo.new(code, name, &block)
-
end
-
-
# Returns a frozen hash of all the countries that have been defined in
-
# the index.
-
1
def countries
-
@countries.freeze
-
end
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# Class to store the data loaded from the country index. Instances of this
-
# class are passed to the blocks in the index that define timezones.
-
1
class CountryInfo #:nodoc:
-
1
attr_reader :code
-
1
attr_reader :name
-
-
# Constructs a new CountryInfo with an ISO 3166 country code, name and
-
# block. The block will be evaluated to obtain the timezones for the country
-
# (when they are first needed).
-
1
def initialize(code, name, &block)
-
@code = code
-
@name = name
-
@block = block
-
@zones = nil
-
@zone_identifiers = nil
-
end
-
-
# Called by the index data to define a timezone for the country.
-
1
def timezone(identifier, latitude_numerator, latitude_denominator,
-
longitude_numerator, longitude_denominator, description = nil)
-
# Currently only store the identifiers.
-
@zones << CountryTimezone.new(identifier, latitude_numerator,
-
latitude_denominator, longitude_numerator, longitude_denominator,
-
description)
-
end
-
-
# Returns a frozen array of all the zone identifiers for the country. These
-
# are in the order they were added using the timezone method.
-
1
def zone_identifiers
-
unless @zone_identifiers
-
@zone_identifiers = zones.collect {|zone| zone.identifier}
-
@zone_identifiers.freeze
-
end
-
-
@zone_identifiers
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
"#<#{self.class}: #@code>"
-
end
-
-
# Returns a frozen array of all the timezones for the for the country as
-
# CountryTimezone instances. These are in the order they were added using
-
# the timezone method.
-
1
def zones
-
unless @zones
-
@zones = []
-
@block.call(self) if @block
-
@block = nil
-
@zones.freeze
-
end
-
-
@zones
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# A Timezone within a Country. This contains extra information about the
-
# Timezone that is specific to the Country (a Timezone could be used by
-
# multiple countries).
-
1
class CountryTimezone
-
# The zone identifier.
-
1
attr_reader :identifier
-
-
# A description of this timezone in relation to the country, e.g.
-
# "Eastern Time". This is usually nil for countries having only a single
-
# Timezone.
-
1
attr_reader :description
-
-
# Creates a new CountryTimezone with a timezone identifier, latitude,
-
# longitude and description. The latitude and longitude are specified as
-
# rationals - a numerator and denominator. For performance reasons, the
-
# numerators and denominators must be specified in their lowest form.
-
#
-
# CountryTimezone instances should not normally be constructed manually.
-
1
def initialize(identifier, latitude_numerator, latitude_denominator,
-
longitude_numerator, longitude_denominator, description = nil) #:nodoc:
-
@identifier = identifier
-
@latitude_numerator = latitude_numerator
-
@latitude_denominator = latitude_denominator
-
@longitude_numerator = longitude_numerator
-
@longitude_denominator = longitude_denominator
-
@description = description
-
end
-
-
# The Timezone (actually a TimezoneProxy for performance reasons).
-
1
def timezone
-
Timezone.get_proxy(@identifier)
-
end
-
-
# if description is not nil, this method returns description; otherwise it
-
# returns timezone.friendly_identifier(true).
-
1
def description_or_friendly_identifier
-
description || timezone.friendly_identifier(true)
-
end
-
-
# The latitude of this timezone in degrees as a Rational.
-
1
def latitude
-
@latitude ||= RubyCoreSupport.rational_new!(@latitude_numerator, @latitude_denominator)
-
end
-
-
# The longitude of this timezone in degrees as a Rational.
-
1
def longitude
-
@longitude ||= RubyCoreSupport.rational_new!(@longitude_numerator, @longitude_denominator)
-
end
-
-
# Returns true if and only if the given CountryTimezone is equal to the
-
# current CountryTimezone (has the same identifer, latitude, longitude
-
# and description).
-
1
def ==(ct)
-
ct.respond_to?(:identifier) && ct.respond_to?(:latitude) &&
-
ct.respond_to?(:longitude) && ct.respond_to?(:description) &&
-
identifier == ct.identifier && latitude == ct.latitude &&
-
longitude == ct.longitude && description == ct.description
-
end
-
-
# Returns true if and only if the given CountryTimezone is equal to the
-
# current CountryTimezone (has the same identifer, latitude, longitude
-
# and description).
-
1
def eql?(ct)
-
self == ct
-
end
-
-
# Returns a hash of this CountryTimezone.
-
1
def hash
-
@identifier.hash ^ @latitude_numerator.hash ^ @latitude_denominator.hash ^
-
@longitude_numerator.hash ^ @longitude_denominator.hash ^ @description.hash
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
"#<#{self.class}: #@identifier>"
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
-
# A Timezone based on a DataTimezoneInfo.
-
1
class DataTimezone < InfoTimezone #:nodoc:
-
-
# Returns the TimezonePeriod for the given UTC time. utc can either be
-
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
-
# information in utc is ignored (it is treated as a UTC time).
-
#
-
# If no TimezonePeriod could be found, PeriodNotFound is raised.
-
1
def period_for_utc(utc)
-
3
info.period_for_utc(utc)
-
end
-
-
# Returns the set of TimezonePeriod instances that are valid for the given
-
# local time as an array. If you just want a single period, use
-
# period_for_local instead and specify how abiguities should be resolved.
-
# Raises PeriodNotFound if no periods are found for the given time.
-
1
def periods_for_local(local)
-
183
info.periods_for_local(local)
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# Thrown if no offsets have been defined when calling period_for_utc or
-
# periods_for_local. Indicates an error in the timezone data.
-
1
class NoOffsetsDefined < StandardError
-
end
-
-
# Represents a (non-linked) timezone defined in a data module.
-
1
class DataTimezoneInfo < TimezoneInfo #:nodoc:
-
-
# Constructs a new TimezoneInfo with its identifier.
-
1
def initialize(identifier)
-
3
super(identifier)
-
3
@offsets = {}
-
3
@transitions = []
-
3
@previous_offset = nil
-
3
@transitions_index = nil
-
end
-
-
# Defines a offset. The id uniquely identifies this offset within the
-
# timezone. utc_offset and std_offset define the offset in seconds of
-
# standard time from UTC and daylight savings from standard time
-
# respectively. abbreviation describes the timezone offset (e.g. GMT, BST,
-
# EST or EDT).
-
#
-
# The first offset to be defined is treated as the offset that applies
-
# until the first transition. This will usually be in Local Mean Time (LMT).
-
#
-
# ArgumentError will be raised if the id is already defined.
-
1
def offset(id, utc_offset, std_offset, abbreviation)
-
15
raise ArgumentError, 'Offset already defined' if @offsets.has_key?(id)
-
-
15
offset = TimezoneOffsetInfo.new(utc_offset, std_offset, abbreviation)
-
15
@offsets[id] = offset
-
15
@previous_offset = offset unless @previous_offset
-
end
-
-
# Defines a transition. Transitions must be defined in chronological order.
-
# ArgumentError will be raised if a transition is added out of order.
-
# offset_id refers to an id defined with offset. ArgumentError will be
-
# raised if the offset_id cannot be found. numerator_or_time and
-
# denomiator specify the time the transition occurs as. See
-
# TimezoneTransitionInfo for more detail about specifying times.
-
1
def transition(year, month, offset_id, numerator_or_time, denominator = nil)
-
176
offset = @offsets[offset_id]
-
176
raise ArgumentError, 'Offset not found' unless offset
-
-
176
if @transitions_index
-
174
if year < @last_year || (year == @last_year && month < @last_month)
-
raise ArgumentError, 'Transitions must be increasing date order'
-
end
-
-
# Record the position of the first transition with this index.
-
174
index = transition_index(year, month)
-
174
@transitions_index[index] ||= @transitions.length
-
-
# Fill in any gaps
-
174
(index - 1).downto(0) do |i|
-
471
break if @transitions_index[i]
-
297
@transitions_index[i] = @transitions.length
-
end
-
else
-
2
@transitions_index = [@transitions.length]
-
2
@start_year = year
-
2
@start_month = month
-
end
-
-
@transitions << TimezoneTransitionInfo.new(offset, @previous_offset,
-
176
numerator_or_time, denominator)
-
176
@last_year = year
-
176
@last_month = month
-
176
@previous_offset = offset
-
end
-
-
# Returns the TimezonePeriod for the given UTC time.
-
# Raises NoOffsetsDefined if no offsets have been defined.
-
1
def period_for_utc(utc)
-
3
unless @transitions.empty?
-
3
utc = TimeOrDateTime.wrap(utc)
-
3
index = transition_index(utc.year, utc.mon)
-
-
3
start_transition = nil
-
3
start = transition_before_end(index)
-
3
if start
-
3
start.downto(0) do |i|
-
3
if @transitions[i].at <= utc
-
3
start_transition = @transitions[i]
-
3
break
-
end
-
end
-
end
-
-
3
end_transition = nil
-
3
start = transition_after_start(index)
-
3
if start
-
1
start.upto(@transitions.length - 1) do |i|
-
2
if @transitions[i].at > utc
-
1
end_transition = @transitions[i]
-
1
break
-
end
-
end
-
end
-
-
3
if start_transition || end_transition
-
3
TimezonePeriod.new(start_transition, end_transition)
-
else
-
# Won't happen since there are transitions. Must always find one
-
# transition that is either >= or < the specified time.
-
raise 'No transitions found in search'
-
end
-
else
-
raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
-
TimezonePeriod.new(nil, nil, @previous_offset)
-
end
-
end
-
-
# Returns the set of TimezonePeriods for the given local time as an array.
-
# Results returned are ordered by increasing UTC start date.
-
# Returns an empty array if no periods are found for the given time.
-
# Raises NoOffsetsDefined if no offsets have been defined.
-
1
def periods_for_local(local)
-
183
unless @transitions.empty?
-
177
local = TimeOrDateTime.wrap(local)
-
177
index = transition_index(local.year, local.mon)
-
-
177
result = []
-
-
177
start_index = transition_after_start(index - 1)
-
177
if start_index && @transitions[start_index].local_end > local
-
if start_index > 0
-
if @transitions[start_index - 1].local_start <= local
-
result << TimezonePeriod.new(@transitions[start_index - 1], @transitions[start_index])
-
end
-
else
-
result << TimezonePeriod.new(nil, @transitions[start_index])
-
end
-
end
-
-
177
end_index = transition_before_end(index + 1)
-
-
177
if end_index
-
177
start_index = end_index unless start_index
-
-
177
start_index.upto(transition_before_end(index + 1)) do |i|
-
295
if @transitions[i].local_start <= local
-
198
if i + 1 < @transitions.length
-
80
if @transitions[i + 1].local_end > local
-
59
result << TimezonePeriod.new(@transitions[i], @transitions[i + 1])
-
end
-
else
-
118
result << TimezonePeriod.new(@transitions[i], nil)
-
end
-
end
-
end
-
end
-
-
177
result
-
else
-
6
raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
-
6
[TimezonePeriod.new(nil, nil, @previous_offset)]
-
end
-
end
-
-
1
private
-
# Returns the index into the @transitions_index array for a given year
-
# and month.
-
1
def transition_index(year, month)
-
354
index = (year - @start_year) * 2
-
354
index += 1 if month > 6
-
354
index -= 1 if @start_month > 6
-
354
index
-
end
-
-
# Returns the index into @transitions of the first transition that occurs
-
# on or after the start of the given index into @transitions_index.
-
# Returns nil if there are no such transitions.
-
1
def transition_after_start(index)
-
180
if index >= @transitions_index.length
-
nil
-
else
-
60
index = 0 if index < 0
-
60
@transitions_index[index]
-
end
-
end
-
-
# Returns the index into @transitions of the first transition that occurs
-
# before the end of the given index into @transitions_index.
-
# Returns nil if there are no such transitions.
-
1
def transition_before_end(index)
-
357
index = index + 1
-
-
357
if index <= 0
-
nil
-
357
elsif index >= @transitions_index.length
-
238
@transitions.length - 1
-
else
-
119
@transitions_index[index] - 1
-
end
-
end
-
end
-
end
-
1
module TZInfo
-
1
module Definitions
-
1
module America
-
1
module Juneau
-
1
include TimezoneDefinition
-
-
1
timezone 'America/Juneau' do |tz|
-
1
tz.offset :o0, 54139, 0, :LMT
-
1
tz.offset :o1, -32261, 0, :LMT
-
1
tz.offset :o2, -28800, 0, :PST
-
1
tz.offset :o3, -28800, 3600, :PWT
-
1
tz.offset :o4, -28800, 3600, :PPT
-
1
tz.offset :o5, -28800, 3600, :PDT
-
1
tz.offset :o6, -32400, 3600, :YDT
-
1
tz.offset :o7, -32400, 0, :YST
-
1
tz.offset :o8, -32400, 0, :AKST
-
1
tz.offset :o9, -32400, 3600, :AKDT
-
-
1
tz.transition 1867, 10, :o1, 207641393861, 86400
-
1
tz.transition 1900, 8, :o2, 208677805061, 86400
-
1
tz.transition 1942, 2, :o3, 29164799, 12
-
1
tz.transition 1945, 8, :o4, 58360379, 24
-
1
tz.transition 1945, 9, :o2, 19453831, 8
-
1
tz.transition 1969, 4, :o5, 29284067, 12
-
1
tz.transition 1969, 10, :o2, 19524167, 8
-
1
tz.transition 1970, 4, :o5, 9972000
-
1
tz.transition 1970, 10, :o2, 25693200
-
1
tz.transition 1971, 4, :o5, 41421600
-
1
tz.transition 1971, 10, :o2, 57747600
-
1
tz.transition 1972, 4, :o5, 73476000
-
1
tz.transition 1972, 10, :o2, 89197200
-
1
tz.transition 1973, 4, :o5, 104925600
-
1
tz.transition 1973, 10, :o2, 120646800
-
1
tz.transition 1974, 1, :o5, 126698400
-
1
tz.transition 1974, 10, :o2, 152096400
-
1
tz.transition 1975, 2, :o5, 162381600
-
1
tz.transition 1975, 10, :o2, 183546000
-
1
tz.transition 1976, 4, :o5, 199274400
-
1
tz.transition 1976, 10, :o2, 215600400
-
1
tz.transition 1977, 4, :o5, 230724000
-
1
tz.transition 1977, 10, :o2, 247050000
-
1
tz.transition 1978, 4, :o5, 262778400
-
1
tz.transition 1978, 10, :o2, 278499600
-
1
tz.transition 1979, 4, :o5, 294228000
-
1
tz.transition 1979, 10, :o2, 309949200
-
1
tz.transition 1980, 4, :o6, 325677600
-
1
tz.transition 1980, 10, :o2, 341402400
-
1
tz.transition 1981, 4, :o5, 357127200
-
1
tz.transition 1981, 10, :o2, 372848400
-
1
tz.transition 1982, 4, :o5, 388576800
-
1
tz.transition 1982, 10, :o2, 404902800
-
1
tz.transition 1983, 4, :o5, 420026400
-
1
tz.transition 1983, 10, :o7, 436352400
-
1
tz.transition 1983, 11, :o8, 439030800
-
1
tz.transition 1984, 4, :o9, 452084400
-
1
tz.transition 1984, 10, :o8, 467805600
-
1
tz.transition 1985, 4, :o9, 483534000
-
1
tz.transition 1985, 10, :o8, 499255200
-
1
tz.transition 1986, 4, :o9, 514983600
-
1
tz.transition 1986, 10, :o8, 530704800
-
1
tz.transition 1987, 4, :o9, 544618800
-
1
tz.transition 1987, 10, :o8, 562154400
-
1
tz.transition 1988, 4, :o9, 576068400
-
1
tz.transition 1988, 10, :o8, 594208800
-
1
tz.transition 1989, 4, :o9, 607518000
-
1
tz.transition 1989, 10, :o8, 625658400
-
1
tz.transition 1990, 4, :o9, 638967600
-
1
tz.transition 1990, 10, :o8, 657108000
-
1
tz.transition 1991, 4, :o9, 671022000
-
1
tz.transition 1991, 10, :o8, 688557600
-
1
tz.transition 1992, 4, :o9, 702471600
-
1
tz.transition 1992, 10, :o8, 720007200
-
1
tz.transition 1993, 4, :o9, 733921200
-
1
tz.transition 1993, 10, :o8, 752061600
-
1
tz.transition 1994, 4, :o9, 765370800
-
1
tz.transition 1994, 10, :o8, 783511200
-
1
tz.transition 1995, 4, :o9, 796820400
-
1
tz.transition 1995, 10, :o8, 814960800
-
1
tz.transition 1996, 4, :o9, 828874800
-
1
tz.transition 1996, 10, :o8, 846410400
-
1
tz.transition 1997, 4, :o9, 860324400
-
1
tz.transition 1997, 10, :o8, 877860000
-
1
tz.transition 1998, 4, :o9, 891774000
-
1
tz.transition 1998, 10, :o8, 909309600
-
1
tz.transition 1999, 4, :o9, 923223600
-
1
tz.transition 1999, 10, :o8, 941364000
-
1
tz.transition 2000, 4, :o9, 954673200
-
1
tz.transition 2000, 10, :o8, 972813600
-
1
tz.transition 2001, 4, :o9, 986122800
-
1
tz.transition 2001, 10, :o8, 1004263200
-
1
tz.transition 2002, 4, :o9, 1018177200
-
1
tz.transition 2002, 10, :o8, 1035712800
-
1
tz.transition 2003, 4, :o9, 1049626800
-
1
tz.transition 2003, 10, :o8, 1067162400
-
1
tz.transition 2004, 4, :o9, 1081076400
-
1
tz.transition 2004, 10, :o8, 1099216800
-
1
tz.transition 2005, 4, :o9, 1112526000
-
1
tz.transition 2005, 10, :o8, 1130666400
-
1
tz.transition 2006, 4, :o9, 1143975600
-
1
tz.transition 2006, 10, :o8, 1162116000
-
1
tz.transition 2007, 3, :o9, 1173610800
-
1
tz.transition 2007, 11, :o8, 1194170400
-
1
tz.transition 2008, 3, :o9, 1205060400
-
1
tz.transition 2008, 11, :o8, 1225620000
-
1
tz.transition 2009, 3, :o9, 1236510000
-
1
tz.transition 2009, 11, :o8, 1257069600
-
1
tz.transition 2010, 3, :o9, 1268564400
-
1
tz.transition 2010, 11, :o8, 1289124000
-
1
tz.transition 2011, 3, :o9, 1300014000
-
1
tz.transition 2011, 11, :o8, 1320573600
-
1
tz.transition 2012, 3, :o9, 1331463600
-
1
tz.transition 2012, 11, :o8, 1352023200
-
1
tz.transition 2013, 3, :o9, 1362913200
-
1
tz.transition 2013, 11, :o8, 1383472800
-
1
tz.transition 2014, 3, :o9, 1394362800
-
1
tz.transition 2014, 11, :o8, 1414922400
-
1
tz.transition 2015, 3, :o9, 1425812400
-
1
tz.transition 2015, 11, :o8, 1446372000
-
1
tz.transition 2016, 3, :o9, 1457866800
-
1
tz.transition 2016, 11, :o8, 1478426400
-
1
tz.transition 2017, 3, :o9, 1489316400
-
1
tz.transition 2017, 11, :o8, 1509876000
-
1
tz.transition 2018, 3, :o9, 1520766000
-
1
tz.transition 2018, 11, :o8, 1541325600
-
1
tz.transition 2019, 3, :o9, 1552215600
-
1
tz.transition 2019, 11, :o8, 1572775200
-
1
tz.transition 2020, 3, :o9, 1583665200
-
1
tz.transition 2020, 11, :o8, 1604224800
-
1
tz.transition 2021, 3, :o9, 1615719600
-
1
tz.transition 2021, 11, :o8, 1636279200
-
1
tz.transition 2022, 3, :o9, 1647169200
-
1
tz.transition 2022, 11, :o8, 1667728800
-
1
tz.transition 2023, 3, :o9, 1678618800
-
1
tz.transition 2023, 11, :o8, 1699178400
-
1
tz.transition 2024, 3, :o9, 1710068400
-
1
tz.transition 2024, 11, :o8, 1730628000
-
1
tz.transition 2025, 3, :o9, 1741518000
-
1
tz.transition 2025, 11, :o8, 1762077600
-
1
tz.transition 2026, 3, :o9, 1772967600
-
1
tz.transition 2026, 11, :o8, 1793527200
-
1
tz.transition 2027, 3, :o9, 1805022000
-
1
tz.transition 2027, 11, :o8, 1825581600
-
1
tz.transition 2028, 3, :o9, 1836471600
-
1
tz.transition 2028, 11, :o8, 1857031200
-
1
tz.transition 2029, 3, :o9, 1867921200
-
1
tz.transition 2029, 11, :o8, 1888480800
-
1
tz.transition 2030, 3, :o9, 1899370800
-
1
tz.transition 2030, 11, :o8, 1919930400
-
1
tz.transition 2031, 3, :o9, 1930820400
-
1
tz.transition 2031, 11, :o8, 1951380000
-
1
tz.transition 2032, 3, :o9, 1962874800
-
1
tz.transition 2032, 11, :o8, 1983434400
-
1
tz.transition 2033, 3, :o9, 1994324400
-
1
tz.transition 2033, 11, :o8, 2014884000
-
1
tz.transition 2034, 3, :o9, 2025774000
-
1
tz.transition 2034, 11, :o8, 2046333600
-
1
tz.transition 2035, 3, :o9, 2057223600
-
1
tz.transition 2035, 11, :o8, 2077783200
-
1
tz.transition 2036, 3, :o9, 2088673200
-
1
tz.transition 2036, 11, :o8, 2109232800
-
1
tz.transition 2037, 3, :o9, 2120122800
-
1
tz.transition 2037, 11, :o8, 2140682400
-
1
tz.transition 2038, 3, :o9, 59171927, 24
-
1
tz.transition 2038, 11, :o8, 29588819, 12
-
1
tz.transition 2039, 3, :o9, 59180663, 24
-
1
tz.transition 2039, 11, :o8, 29593187, 12
-
1
tz.transition 2040, 3, :o9, 59189399, 24
-
1
tz.transition 2040, 11, :o8, 29597555, 12
-
1
tz.transition 2041, 3, :o9, 59198135, 24
-
1
tz.transition 2041, 11, :o8, 29601923, 12
-
1
tz.transition 2042, 3, :o9, 59206871, 24
-
1
tz.transition 2042, 11, :o8, 29606291, 12
-
1
tz.transition 2043, 3, :o9, 59215607, 24
-
1
tz.transition 2043, 11, :o8, 29610659, 12
-
1
tz.transition 2044, 3, :o9, 59224511, 24
-
1
tz.transition 2044, 11, :o8, 29615111, 12
-
1
tz.transition 2045, 3, :o9, 59233247, 24
-
1
tz.transition 2045, 11, :o8, 29619479, 12
-
1
tz.transition 2046, 3, :o9, 59241983, 24
-
1
tz.transition 2046, 11, :o8, 29623847, 12
-
1
tz.transition 2047, 3, :o9, 59250719, 24
-
1
tz.transition 2047, 11, :o8, 29628215, 12
-
1
tz.transition 2048, 3, :o9, 59259455, 24
-
1
tz.transition 2048, 11, :o8, 29632583, 12
-
1
tz.transition 2049, 3, :o9, 59268359, 24
-
1
tz.transition 2049, 11, :o8, 29637035, 12
-
1
tz.transition 2050, 3, :o9, 59277095, 24
-
1
tz.transition 2050, 11, :o8, 29641403, 12
-
end
-
end
-
end
-
end
-
end
-
1
module TZInfo
-
1
module Definitions
-
1
module Etc
-
1
module UTC
-
1
include TimezoneDefinition
-
-
1
timezone 'Etc/UTC' do |tz|
-
1
tz.offset :o0, 0, 0, :UTC
-
-
end
-
end
-
end
-
end
-
end
-
1
module TZInfo
-
1
module Definitions
-
1
module Pacific
-
1
module Honolulu
-
1
include TimezoneDefinition
-
-
1
timezone 'Pacific/Honolulu' do |tz|
-
1
tz.offset :o0, -37886, 0, :LMT
-
1
tz.offset :o1, -37800, 0, :HST
-
1
tz.offset :o2, -37800, 3600, :HDT
-
1
tz.offset :o3, -36000, 0, :HST
-
-
1
tz.transition 1896, 1, :o1, 104266329343, 43200
-
1
tz.transition 1933, 4, :o2, 116505265, 48
-
1
tz.transition 1933, 5, :o1, 116506291, 48
-
1
tz.transition 1942, 2, :o2, 116659201, 48
-
1
tz.transition 1945, 9, :o1, 116722991, 48
-
1
tz.transition 1947, 6, :o3, 116752561, 48
-
end
-
end
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
-
# A Timezone based on a TimezoneInfo.
-
1
class InfoTimezone < Timezone #:nodoc:
-
-
# Constructs a new InfoTimezone with a TimezoneInfo instance.
-
1
def self.new(info)
-
3
tz = super()
-
3
tz.send(:setup, info)
-
3
tz
-
end
-
-
# The identifier of the timezone, e.g. "Europe/Paris".
-
1
def identifier
-
3
@info.identifier
-
end
-
-
1
protected
-
# The TimezoneInfo for this Timezone.
-
1
def info
-
186
@info
-
end
-
-
1
def setup(info)
-
3
@info = info
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
-
1
class LinkedTimezone < InfoTimezone #:nodoc:
-
# Returns the TimezonePeriod for the given UTC time. utc can either be
-
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
-
# information in utc is ignored (it is treated as a UTC time).
-
#
-
# If no TimezonePeriod could be found, PeriodNotFound is raised.
-
1
def period_for_utc(utc)
-
@linked_timezone.period_for_utc(utc)
-
end
-
-
# Returns the set of TimezonePeriod instances that are valid for the given
-
# local time as an array. If you just want a single period, use
-
# period_for_local instead and specify how abiguities should be resolved.
-
# Raises PeriodNotFound if no periods are found for the given time.
-
1
def periods_for_local(local)
-
@linked_timezone.periods_for_local(local)
-
end
-
-
1
protected
-
1
def setup(info)
-
super(info)
-
@linked_timezone = Timezone.get(info.link_to_identifier)
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# Represents a linked timezone defined in a data module.
-
1
class LinkedTimezoneInfo < TimezoneInfo #:nodoc:
-
-
# The zone that provides the data (that this zone is an alias for).
-
1
attr_reader :link_to_identifier
-
-
# Constructs a new TimezoneInfo with an identifier and the identifier
-
# of the zone linked to.
-
1
def initialize(identifier, link_to_identifier)
-
super(identifier)
-
@link_to_identifier = link_to_identifier
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
"#<#{self.class}: #@identifier,#@link_to_identifier>"
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
require 'rational' unless defined?(Rational)
-
-
1
module TZInfo
-
-
# Provides a method for getting Rationals for a timezone offset in seconds.
-
# Pre-reduced rationals are returned for all the half-hour intervals between
-
# -14 and +14 hours to avoid having to call gcd at runtime.
-
1
module OffsetRationals #:nodoc:
-
1
@@rational_cache = {
-
-50400 => RubyCoreSupport.rational_new!(-7,12),
-
-48600 => RubyCoreSupport.rational_new!(-9,16),
-
-46800 => RubyCoreSupport.rational_new!(-13,24),
-
-45000 => RubyCoreSupport.rational_new!(-25,48),
-
-43200 => RubyCoreSupport.rational_new!(-1,2),
-
-41400 => RubyCoreSupport.rational_new!(-23,48),
-
-39600 => RubyCoreSupport.rational_new!(-11,24),
-
-37800 => RubyCoreSupport.rational_new!(-7,16),
-
-36000 => RubyCoreSupport.rational_new!(-5,12),
-
-34200 => RubyCoreSupport.rational_new!(-19,48),
-
-32400 => RubyCoreSupport.rational_new!(-3,8),
-
-30600 => RubyCoreSupport.rational_new!(-17,48),
-
-28800 => RubyCoreSupport.rational_new!(-1,3),
-
-27000 => RubyCoreSupport.rational_new!(-5,16),
-
-25200 => RubyCoreSupport.rational_new!(-7,24),
-
-23400 => RubyCoreSupport.rational_new!(-13,48),
-
-21600 => RubyCoreSupport.rational_new!(-1,4),
-
-19800 => RubyCoreSupport.rational_new!(-11,48),
-
-18000 => RubyCoreSupport.rational_new!(-5,24),
-
-16200 => RubyCoreSupport.rational_new!(-3,16),
-
-14400 => RubyCoreSupport.rational_new!(-1,6),
-
-12600 => RubyCoreSupport.rational_new!(-7,48),
-
-10800 => RubyCoreSupport.rational_new!(-1,8),
-
-9000 => RubyCoreSupport.rational_new!(-5,48),
-
-7200 => RubyCoreSupport.rational_new!(-1,12),
-
-5400 => RubyCoreSupport.rational_new!(-1,16),
-
-3600 => RubyCoreSupport.rational_new!(-1,24),
-
-1800 => RubyCoreSupport.rational_new!(-1,48),
-
0 => RubyCoreSupport.rational_new!(0,1),
-
1800 => RubyCoreSupport.rational_new!(1,48),
-
3600 => RubyCoreSupport.rational_new!(1,24),
-
5400 => RubyCoreSupport.rational_new!(1,16),
-
7200 => RubyCoreSupport.rational_new!(1,12),
-
9000 => RubyCoreSupport.rational_new!(5,48),
-
10800 => RubyCoreSupport.rational_new!(1,8),
-
12600 => RubyCoreSupport.rational_new!(7,48),
-
14400 => RubyCoreSupport.rational_new!(1,6),
-
16200 => RubyCoreSupport.rational_new!(3,16),
-
18000 => RubyCoreSupport.rational_new!(5,24),
-
19800 => RubyCoreSupport.rational_new!(11,48),
-
21600 => RubyCoreSupport.rational_new!(1,4),
-
23400 => RubyCoreSupport.rational_new!(13,48),
-
25200 => RubyCoreSupport.rational_new!(7,24),
-
27000 => RubyCoreSupport.rational_new!(5,16),
-
28800 => RubyCoreSupport.rational_new!(1,3),
-
30600 => RubyCoreSupport.rational_new!(17,48),
-
32400 => RubyCoreSupport.rational_new!(3,8),
-
34200 => RubyCoreSupport.rational_new!(19,48),
-
36000 => RubyCoreSupport.rational_new!(5,12),
-
37800 => RubyCoreSupport.rational_new!(7,16),
-
39600 => RubyCoreSupport.rational_new!(11,24),
-
41400 => RubyCoreSupport.rational_new!(23,48),
-
43200 => RubyCoreSupport.rational_new!(1,2),
-
45000 => RubyCoreSupport.rational_new!(25,48),
-
46800 => RubyCoreSupport.rational_new!(13,24),
-
48600 => RubyCoreSupport.rational_new!(9,16),
-
50400 => RubyCoreSupport.rational_new!(7,12)}
-
-
# Returns a Rational expressing the fraction of a day that offset in
-
# seconds represents (i.e. equivalent to Rational(offset, 86400)).
-
1
def rational_for_offset(offset)
-
1
@@rational_cache[offset] || Rational(offset, 86400)
-
end
-
1
module_function :rational_for_offset
-
end
-
end
-
#--
-
# Copyright (c) 2008-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
require 'date'
-
1
require 'rational' unless defined?(Rational)
-
-
1
module TZInfo
-
-
# Methods to support different versions of Ruby.
-
1
module RubyCoreSupport #:nodoc:
-
-
# Use Rational.new! for performance reasons in Ruby 1.8.
-
# This has been removed from 1.9, but Rational performs better.
-
1
if Rational.respond_to? :new!
-
def self.rational_new!(numerator, denominator = 1)
-
Rational.new!(numerator, denominator)
-
end
-
else
-
1
def self.rational_new!(numerator, denominator = 1)
-
59
Rational(numerator, denominator)
-
end
-
end
-
-
# Ruby 1.8.6 introduced new! and deprecated new0.
-
# Ruby 1.9.0 removed new0.
-
# Ruby trunk revision 31668 removed the new! method.
-
# Still support new0 for better performance on older versions of Ruby (new0 indicates
-
# that the rational has already been reduced to its lowest terms).
-
# Fallback to jd with conversion from ajd if new! and new0 are unavailable.
-
1
if DateTime.respond_to? :new!
-
def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
-
DateTime.new!(ajd, of, sg)
-
end
-
elsif DateTime.respond_to? :new0
-
def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
-
DateTime.new0(ajd, of, sg)
-
end
-
else
-
1
HALF_DAYS_IN_DAY = rational_new!(1, 2)
-
-
1
def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
-
# Convert from an Astronomical Julian Day number to a civil Julian Day number.
-
1
jd = ajd + of + HALF_DAYS_IN_DAY
-
-
# Ruby trunk revision 31862 changed the behaviour of DateTime.jd so that it will no
-
# longer accept a fractional civil Julian Day number if further arguments are specified.
-
# Calculate the hours, minutes and seconds to pass to jd.
-
-
1
jd_i = jd.to_i
-
1
jd_i -= 1 if jd < 0
-
1
hours = (jd - jd_i) * 24
-
1
hours_i = hours.to_i
-
1
minutes = (hours - hours_i) * 60
-
1
minutes_i = minutes.to_i
-
1
seconds = (minutes - minutes_i) * 60
-
-
1
DateTime.jd(jd_i, hours_i, minutes_i, seconds, of, sg)
-
end
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
require 'date'
-
1
require 'time'
-
-
1
module TZInfo
-
# Used by TZInfo internally to represent either a Time, DateTime or integer
-
# timestamp (seconds since 1970-01-01 00:00:00).
-
1
class TimeOrDateTime #:nodoc:
-
1
include Comparable
-
-
# Constructs a new TimeOrDateTime. timeOrDateTime can be a Time, DateTime
-
# or an integer. If using a Time or DateTime, any time zone information is
-
# ignored.
-
1
def initialize(timeOrDateTime)
-
398
@time = nil
-
398
@datetime = nil
-
398
@timestamp = nil
-
-
398
if timeOrDateTime.is_a?(Time)
-
342
@time = timeOrDateTime
-
342
@time = Time.utc(@time.year, @time.mon, @time.mday, @time.hour, @time.min, @time.sec) unless @time.zone == 'UTC'
-
342
@orig = @time
-
56
elsif timeOrDateTime.is_a?(DateTime)
-
2
@datetime = timeOrDateTime
-
2
@datetime = @datetime.new_offset(0) unless @datetime.offset == 0
-
2
@orig = @datetime
-
else
-
54
@timestamp = timeOrDateTime.to_i
-
54
@orig = @timestamp
-
end
-
end
-
-
# Returns the time as a Time.
-
1
def to_time
-
719
unless @time
-
37
if @timestamp
-
37
@time = Time.at(@timestamp).utc
-
else
-
@time = Time.utc(year, mon, mday, hour, min, sec)
-
end
-
end
-
-
719
@time
-
end
-
-
# Returns the time as a DateTime.
-
1
def to_datetime
-
240
unless @datetime
-
120
@datetime = DateTime.new(year, mon, mday, hour, min, sec)
-
end
-
-
240
@datetime
-
end
-
-
# Returns the time as an integer timestamp.
-
1
def to_i
-
35
unless @timestamp
-
@timestamp = to_time.to_i
-
end
-
-
35
@timestamp
-
end
-
-
# Returns the time as the original time passed to new.
-
1
def to_orig
-
439
@orig
-
end
-
-
# Returns a string representation of the TimeOrDateTime.
-
1
def to_s
-
if @orig.is_a?(Time)
-
"Time: #{@orig.to_s}"
-
elsif @orig.is_a?(DateTime)
-
"DateTime: #{@orig.to_s}"
-
else
-
"Timestamp: #{@orig.to_s}"
-
end
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
"#<#{self.class}: #{@orig.inspect}>"
-
end
-
-
# Returns the year.
-
1
def year
-
300
if @time
-
300
@time.year
-
elsif @datetime
-
@datetime.year
-
else
-
to_time.year
-
end
-
end
-
-
# Returns the month of the year (1..12).
-
1
def mon
-
300
if @time
-
300
@time.mon
-
elsif @datetime
-
@datetime.mon
-
else
-
to_time.mon
-
end
-
end
-
1
alias :month :mon
-
-
# Returns the day of the month (1..n).
-
1
def mday
-
120
if @time
-
120
@time.mday
-
elsif @datetime
-
@datetime.mday
-
else
-
to_time.mday
-
end
-
end
-
1
alias :day :mday
-
-
# Returns the hour of the day (0..23).
-
1
def hour
-
120
if @time
-
120
@time.hour
-
elsif @datetime
-
@datetime.hour
-
else
-
to_time.hour
-
end
-
end
-
-
# Returns the minute of the hour (0..59).
-
1
def min
-
120
if @time
-
120
@time.min
-
elsif @datetime
-
@datetime.min
-
else
-
to_time.min
-
end
-
end
-
-
# Returns the second of the minute (0..60). (60 for a leap second).
-
1
def sec
-
120
if @time
-
120
@time.sec
-
elsif @datetime
-
@datetime.sec
-
else
-
to_time.sec
-
end
-
end
-
-
# Compares this TimeOrDateTime with another Time, DateTime, integer
-
# timestamp or TimeOrDateTime. Returns -1, 0 or +1 depending whether the
-
# receiver is less than, equal to, or greater than timeOrDateTime.
-
#
-
# Milliseconds and smaller units are ignored in the comparison.
-
1
def <=>(timeOrDateTime)
-
439
if timeOrDateTime.is_a?(TimeOrDateTime)
-
439
orig = timeOrDateTime.to_orig
-
-
439
if @orig.is_a?(DateTime) || orig.is_a?(DateTime)
-
# If either is a DateTime, assume it is there for a reason
-
# (i.e. for range).
-
120
to_datetime <=> timeOrDateTime.to_datetime
-
319
elsif orig.is_a?(Time)
-
319
to_time <=> timeOrDateTime.to_time
-
else
-
to_i <=> timeOrDateTime.to_i
-
end
-
elsif @orig.is_a?(DateTime) || timeOrDateTime.is_a?(DateTime)
-
# If either is a DateTime, assume it is there for a reason
-
# (i.e. for range).
-
to_datetime <=> TimeOrDateTime.wrap(timeOrDateTime).to_datetime
-
elsif timeOrDateTime.is_a?(Time)
-
to_time <=> timeOrDateTime
-
else
-
to_i <=> timeOrDateTime.to_i
-
end
-
end
-
-
# Adds a number of seconds to the TimeOrDateTime. Returns a new
-
# TimeOrDateTime, preserving what the original constructed type was.
-
# If the original type is a Time and the resulting calculation goes out of
-
# range for Times, then an exception will be raised by the Time class.
-
1
def +(seconds)
-
81
if seconds == 0
-
self
-
else
-
81
if @orig.is_a?(DateTime)
-
TimeOrDateTime.new(@orig + OffsetRationals.rational_for_offset(seconds))
-
else
-
# + defined for Time and integer timestamps
-
81
TimeOrDateTime.new(@orig + seconds)
-
end
-
end
-
end
-
-
# Subtracts a number of seconds from the TimeOrDateTime. Returns a new
-
# TimeOrDateTime, preserving what the original constructed type was.
-
# If the original type is a Time and the resulting calculation goes out of
-
# range for Times, then an exception will be raised by the Time class.
-
1
def -(seconds)
-
78
self + (-seconds)
-
end
-
-
# Similar to the + operator, but for cases where adding would cause a
-
# timestamp or time to go out of the allowed range, converts to a DateTime
-
# based TimeOrDateTime.
-
1
def add_with_convert(seconds)
-
36
if seconds == 0
-
self
-
else
-
36
if @orig.is_a?(DateTime)
-
1
TimeOrDateTime.new(@orig + OffsetRationals.rational_for_offset(seconds))
-
else
-
# A Time or timestamp.
-
35
result = to_i + seconds
-
-
35
if result < 0 || result > 2147483647
-
result = TimeOrDateTime.new(to_datetime + OffsetRationals.rational_for_offset(seconds))
-
else
-
35
result = TimeOrDateTime.new(@orig + seconds)
-
end
-
end
-
end
-
end
-
-
# Returns true if todt represents the same time and was originally
-
# constructed with the same type (DateTime, Time or timestamp) as this
-
# TimeOrDateTime.
-
1
def eql?(todt)
-
todt.respond_to?(:to_orig) && to_orig.eql?(todt.to_orig)
-
end
-
-
# Returns a hash of this TimeOrDateTime.
-
1
def hash
-
@orig.hash
-
end
-
-
# If no block is given, returns a TimeOrDateTime wrapping the given
-
# timeOrDateTime. If a block is specified, a TimeOrDateTime is constructed
-
# and passed to the block. The result of the block must be a TimeOrDateTime.
-
# to_orig will be called on the result and the result of to_orig will be
-
# returned.
-
#
-
# timeOrDateTime can be a Time, DateTime, integer timestamp or TimeOrDateTime.
-
# If a TimeOrDateTime is passed in, no new TimeOrDateTime will be constructed,
-
# the passed in value will be used.
-
1
def self.wrap(timeOrDateTime)
-
261
t = timeOrDateTime.is_a?(TimeOrDateTime) ? timeOrDateTime : TimeOrDateTime.new(timeOrDateTime)
-
-
261
if block_given?
-
81
t = yield t
-
-
81
if timeOrDateTime.is_a?(TimeOrDateTime)
-
t
-
81
elsif timeOrDateTime.is_a?(Time)
-
81
t.to_time
-
elsif timeOrDateTime.is_a?(DateTime)
-
t.to_datetime
-
else
-
t.to_i
-
end
-
else
-
180
t
-
end
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2005-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
require 'date'
-
-
1
module TZInfo
-
# Indicate a specified time in a local timezone has more than one
-
# possible time in UTC. This happens when switching from daylight savings time
-
# to normal time where the clocks are rolled back. Thrown by period_for_local
-
# and local_to_utc when using an ambiguous time and not specifying any
-
# means to resolve the ambiguity.
-
1
class AmbiguousTime < StandardError
-
end
-
-
# Thrown to indicate that no TimezonePeriod matching a given time could be found.
-
1
class PeriodNotFound < StandardError
-
end
-
-
# Thrown by Timezone#get if the identifier given is not valid.
-
1
class InvalidTimezoneIdentifier < StandardError
-
end
-
-
# Thrown if an attempt is made to use a timezone created with Timezone.new(nil).
-
1
class UnknownTimezone < StandardError
-
end
-
-
# Timezone is the base class of all timezones. It provides a factory method
-
# get to access timezones by identifier. Once a specific Timezone has been
-
# retrieved, DateTimes, Times and timestamps can be converted between the UTC
-
# and the local time for the zone. For example:
-
#
-
# tz = TZInfo::Timezone.get('America/New_York')
-
# puts tz.utc_to_local(DateTime.new(2005,8,29,15,35,0)).to_s
-
# puts tz.local_to_utc(Time.utc(2005,8,29,11,35,0)).to_s
-
# puts tz.utc_to_local(1125315300).to_s
-
#
-
# Each time conversion method returns an object of the same type it was
-
# passed.
-
#
-
# The timezone information all comes from the tz database
-
# (see http://www.twinsun.com/tz/tz-link.htm)
-
1
class Timezone
-
1
include Comparable
-
-
# Cache of loaded zones by identifier to avoid using require if a zone
-
# has already been loaded.
-
1
@@loaded_zones = {}
-
-
# Whether the timezones index has been loaded yet.
-
1
@@index_loaded = false
-
-
# Default value of the dst parameter of the local_to_utc and
-
# period_for_local methods.
-
1
@@default_dst = nil
-
-
# Sets the default value of the optional dst parameter of the
-
# local_to_utc and period_for_local methods. Can be set to nil, true or
-
# false.
-
#
-
# The value of default_dst defaults to nil if unset.
-
1
def self.default_dst=(value)
-
@@default_dst = value.nil? ? nil : !!value
-
end
-
-
# Gets the default value of the optional dst parameter of the
-
# local_to_utc and period_for_local methods. Can be set to nil, true or
-
# false.
-
1
def self.default_dst
-
@@default_dst
-
end
-
-
# Returns a timezone by its identifier (e.g. "Europe/London",
-
# "America/Chicago" or "UTC").
-
#
-
# Raises InvalidTimezoneIdentifier if the timezone couldn't be found.
-
1
def self.get(identifier)
-
3
instance = @@loaded_zones[identifier]
-
3
unless instance
-
3
raise InvalidTimezoneIdentifier, 'Invalid identifier' if identifier !~ /^[A-Za-z0-9\+\-_]+(\/[A-Za-z0-9\+\-_]+)*$/
-
3
identifier = identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__')
-
3
begin
-
# Use a temporary variable to avoid an rdoc warning
-
3
file = "tzinfo/definitions/#{identifier}".untaint
-
3
require file
-
-
3
m = Definitions
-
3
identifier.split(/\//).each {|part|
-
6
m = m.const_get(part)
-
}
-
-
3
info = m.get
-
-
# Could make Timezone subclasses register an interest in an info
-
# type. Since there are currently only two however, there isn't
-
# much point.
-
3
if info.kind_of?(DataTimezoneInfo)
-
3
instance = DataTimezone.new(info)
-
elsif info.kind_of?(LinkedTimezoneInfo)
-
instance = LinkedTimezone.new(info)
-
else
-
raise InvalidTimezoneIdentifier, "No handler for info type #{info.class}"
-
end
-
-
3
@@loaded_zones[instance.identifier] = instance
-
rescue LoadError, NameError => e
-
raise InvalidTimezoneIdentifier, e.message
-
end
-
end
-
-
3
instance
-
end
-
-
# Returns a proxy for the Timezone with the given identifier. The proxy
-
# will cause the real timezone to be loaded when an attempt is made to
-
# find a period or convert a time. get_proxy will not validate the
-
# identifier. If an invalid identifier is specified, no exception will be
-
# raised until the proxy is used.
-
1
def self.get_proxy(identifier)
-
TimezoneProxy.new(identifier)
-
end
-
-
# If identifier is nil calls super(), otherwise calls get. An identfier
-
# should always be passed in when called externally.
-
1
def self.new(identifier = nil)
-
7
if identifier
-
get(identifier)
-
else
-
7
super()
-
end
-
end
-
-
# Returns an array containing all the available Timezones.
-
#
-
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
-
# definitions until a conversion is actually required.
-
1
def self.all
-
get_proxies(all_identifiers)
-
end
-
-
# Returns an array containing the identifiers of all the available
-
# Timezones.
-
1
def self.all_identifiers
-
load_index
-
Indexes::Timezones.timezones
-
end
-
-
# Returns an array containing all the available Timezones that are based
-
# on data (are not links to other Timezones).
-
#
-
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
-
# definitions until a conversion is actually required.
-
1
def self.all_data_zones
-
get_proxies(all_data_zone_identifiers)
-
end
-
-
# Returns an array containing the identifiers of all the available
-
# Timezones that are based on data (are not links to other Timezones)..
-
1
def self.all_data_zone_identifiers
-
load_index
-
Indexes::Timezones.data_timezones
-
end
-
-
# Returns an array containing all the available Timezones that are links
-
# to other Timezones.
-
#
-
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
-
# definitions until a conversion is actually required.
-
1
def self.all_linked_zones
-
get_proxies(all_linked_zone_identifiers)
-
end
-
-
# Returns an array containing the identifiers of all the available
-
# Timezones that are links to other Timezones.
-
1
def self.all_linked_zone_identifiers
-
load_index
-
Indexes::Timezones.linked_timezones
-
end
-
-
# Returns all the Timezones defined for all Countries. This is not the
-
# complete set of Timezones as some are not country specific (e.g.
-
# 'Etc/GMT').
-
#
-
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
-
# definitions until a conversion is actually required.
-
1
def self.all_country_zones
-
Country.all_codes.inject([]) {|zones,country|
-
zones += Country.get(country).zones
-
}
-
end
-
-
# Returns all the zone identifiers defined for all Countries. This is not the
-
# complete set of zone identifiers as some are not country specific (e.g.
-
# 'Etc/GMT'). You can obtain a Timezone instance for a given identifier
-
# with the get method.
-
1
def self.all_country_zone_identifiers
-
Country.all_codes.inject([]) {|zones,country|
-
zones += Country.get(country).zone_identifiers
-
}
-
end
-
-
# Returns all US Timezone instances. A shortcut for
-
# TZInfo::Country.get('US').zones.
-
#
-
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
-
# definitions until a conversion is actually required.
-
1
def self.us_zones
-
Country.get('US').zones
-
end
-
-
# Returns all US zone identifiers. A shortcut for
-
# TZInfo::Country.get('US').zone_identifiers.
-
1
def self.us_zone_identifiers
-
Country.get('US').zone_identifiers
-
end
-
-
# The identifier of the timezone, e.g. "Europe/Paris".
-
1
def identifier
-
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
-
end
-
-
# An alias for identifier.
-
1
def name
-
# Don't use alias, as identifier gets overridden.
-
identifier
-
end
-
-
# Returns a friendlier version of the identifier.
-
1
def to_s
-
friendly_identifier
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
"#<#{self.class}: #{identifier}>"
-
end
-
-
# Returns a friendlier version of the identifier. Set skip_first_part to
-
# omit the first part of the identifier (typically a region name) where
-
# there is more than one part.
-
#
-
# For example:
-
#
-
# Timezone.get('Europe/Paris').friendly_identifier(false) #=> "Europe - Paris"
-
# Timezone.get('Europe/Paris').friendly_identifier(true) #=> "Paris"
-
# Timezone.get('America/Indiana/Knox').friendly_identifier(false) #=> "America - Knox, Indiana"
-
# Timezone.get('America/Indiana/Knox').friendly_identifier(true) #=> "Knox, Indiana"
-
1
def friendly_identifier(skip_first_part = false)
-
parts = identifier.split('/')
-
if parts.empty?
-
# shouldn't happen
-
identifier
-
elsif parts.length == 1
-
parts[0]
-
else
-
if skip_first_part
-
result = ''
-
else
-
result = parts[0] + ' - '
-
end
-
-
parts[1, parts.length - 1].reverse_each {|part|
-
part.gsub!(/_/, ' ')
-
-
if part.index(/[a-z]/)
-
# Missing a space if a lower case followed by an upper case and the
-
# name isn't McXxxx.
-
part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2')
-
part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2')
-
-
# Missing an apostrophe if two consecutive upper case characters.
-
part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
-
end
-
-
result << part
-
result << ', '
-
}
-
-
result.slice!(result.length - 2, 2)
-
result
-
end
-
end
-
-
# Returns the TimezonePeriod for the given UTC time. utc can either be
-
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
-
# information in utc is ignored (it is treated as a UTC time).
-
1
def period_for_utc(utc)
-
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
-
end
-
-
# Returns the set of TimezonePeriod instances that are valid for the given
-
# local time as an array. If you just want a single period, use
-
# period_for_local instead and specify how ambiguities should be resolved.
-
# Returns an empty array if no periods are found for the given time.
-
1
def periods_for_local(local)
-
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
-
end
-
-
# Returns the TimezonePeriod for the given local time. local can either be
-
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
-
# information in local is ignored (it is treated as a time in the current
-
# timezone).
-
#
-
# Warning: There are local times that have no equivalent UTC times (e.g.
-
# in the transition from standard time to daylight savings time). There are
-
# also local times that have more than one UTC equivalent (e.g. in the
-
# transition from daylight savings time to standard time).
-
#
-
# In the first case (no equivalent UTC time), a PeriodNotFound exception
-
# will be raised.
-
#
-
# In the second case (more than one equivalent UTC time), an AmbiguousTime
-
# exception will be raised unless the optional dst parameter or block
-
# handles the ambiguity.
-
#
-
# If the ambiguity is due to a transition from daylight savings time to
-
# standard time, the dst parameter can be used to select whether the
-
# daylight savings time or local time is used. For example,
-
#
-
# Timezone.get('America/New_York').period_for_local(DateTime.new(2004,10,31,1,30,0))
-
#
-
# would raise an AmbiguousTime exception.
-
#
-
# Specifying dst=true would the daylight savings period from April to
-
# October 2004. Specifying dst=false would return the standard period
-
# from October 2004 to April 2005.
-
#
-
# If the dst parameter does not resolve the ambiguity, and a block is
-
# specified, it is called. The block must take a single parameter - an
-
# array of the periods that need to be resolved. The block can select and
-
# return a single period or return nil or an empty array
-
# to cause an AmbiguousTime exception to be raised.
-
#
-
# The default value of the dst parameter can be specified by setting
-
# Timezone.default_dst. If default_dst is not set, or is set to nil, then
-
# an AmbiguousTime exception will be raised in ambiguous situations unless
-
# a block is given to resolve the ambiguity.
-
1
def period_for_local(local, dst = Timezone.default_dst)
-
183
results = periods_for_local(local)
-
-
183
if results.empty?
-
raise PeriodNotFound
-
183
elsif results.size < 2
-
183
results.first
-
else
-
# ambiguous result try to resolve
-
-
if !dst.nil?
-
matches = results.find_all {|period| period.dst? == dst}
-
results = matches if !matches.empty?
-
end
-
-
if results.size < 2
-
results.first
-
else
-
# still ambiguous, try the block
-
-
if block_given?
-
results = yield results
-
end
-
-
if results.is_a?(TimezonePeriod)
-
results
-
elsif results && results.size == 1
-
results.first
-
else
-
raise AmbiguousTime, "#{local} is an ambiguous local time."
-
end
-
end
-
end
-
end
-
-
# Converts a time in UTC to the local timezone. utc can either be
-
# a DateTime, Time or timestamp (Time.to_i). The returned time has the same
-
# type as utc. Any timezone information in utc is ignored (it is treated as
-
# a UTC time).
-
1
def utc_to_local(utc)
-
TimeOrDateTime.wrap(utc) {|wrapped|
-
period_for_utc(wrapped).to_local(wrapped)
-
}
-
end
-
-
# Converts a time in the local timezone to UTC. local can either be
-
# a DateTime, Time or timestamp (Time.to_i). The returned time has the same
-
# type as local. Any timezone information in local is ignored (it is treated
-
# as a local time).
-
#
-
# Warning: There are local times that have no equivalent UTC times (e.g.
-
# in the transition from standard time to daylight savings time). There are
-
# also local times that have more than one UTC equivalent (e.g. in the
-
# transition from daylight savings time to standard time).
-
#
-
# In the first case (no equivalent UTC time), a PeriodNotFound exception
-
# will be raised.
-
#
-
# In the second case (more than one equivalent UTC time), an AmbiguousTime
-
# exception will be raised unless the optional dst parameter or block
-
# handles the ambiguity.
-
#
-
# If the ambiguity is due to a transition from daylight savings time to
-
# standard time, the dst parameter can be used to select whether the
-
# daylight savings time or local time is used. For example,
-
#
-
# Timezone.get('America/New_York').local_to_utc(DateTime.new(2004,10,31,1,30,0))
-
#
-
# would raise an AmbiguousTime exception.
-
#
-
# Specifying dst=true would return 2004-10-31 5:30:00. Specifying dst=false
-
# would return 2004-10-31 6:30:00.
-
#
-
# If the dst parameter does not resolve the ambiguity, and a block is
-
# specified, it is called. The block must take a single parameter - an
-
# array of the periods that need to be resolved. The block can return a
-
# single period to use to convert the time or return nil or an empty array
-
# to cause an AmbiguousTime exception to be raised.
-
#
-
# The default value of the dst parameter can be specified by setting
-
# Timezone.default_dst. If default_dst is not set, or is set to nil, then
-
# an AmbiguousTime exception will be raised in ambiguous situations unless
-
# a block is given to resolve the ambiguity.
-
1
def local_to_utc(local, dst = Timezone.default_dst)
-
TimeOrDateTime.wrap(local) {|wrapped|
-
if block_given?
-
period = period_for_local(wrapped, dst) {|periods| yield periods }
-
else
-
period = period_for_local(wrapped, dst)
-
end
-
-
period.to_utc(wrapped)
-
}
-
end
-
-
# Returns the current time in the timezone as a Time.
-
1
def now
-
utc_to_local(Time.now.utc)
-
end
-
-
# Returns the TimezonePeriod for the current time.
-
1
def current_period
-
period_for_utc(Time.now.utc)
-
end
-
-
# Returns the current Time and TimezonePeriod as an array. The first element
-
# is the time, the second element is the period.
-
1
def current_period_and_time
-
utc = Time.now.utc
-
period = period_for_utc(utc)
-
[period.to_local(utc), period]
-
end
-
-
1
alias :current_time_and_period :current_period_and_time
-
-
# Converts a time in UTC to local time and returns it as a string
-
# according to the given format. The formatting is identical to
-
# Time.strftime and DateTime.strftime, except %Z is replaced with the
-
# timezone abbreviation for the specified time (for example, EST or EDT).
-
1
def strftime(format, utc = Time.now.utc)
-
period = period_for_utc(utc)
-
local = period.to_local(utc)
-
local = Time.at(local).utc unless local.kind_of?(Time) || local.kind_of?(DateTime)
-
abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
-
-
format = format.gsub(/(.?)%Z/) do
-
if $1 == '%'
-
# return %%Z so the real strftime treats it as a literal %Z too
-
'%%Z'
-
else
-
"#$1#{abbreviation}"
-
end
-
end
-
-
local.strftime(format)
-
end
-
-
# Compares two Timezones based on their identifier. Returns -1 if tz is less
-
# than self, 0 if tz is equal to self and +1 if tz is greater than self.
-
1
def <=>(tz)
-
identifier <=> tz.identifier
-
end
-
-
# Returns true if and only if the identifier of tz is equal to the
-
# identifier of this Timezone.
-
1
def eql?(tz)
-
self == tz
-
end
-
-
# Returns a hash of this Timezone.
-
1
def hash
-
identifier.hash
-
end
-
-
# Dumps this Timezone for marshalling.
-
1
def _dump(limit)
-
identifier
-
end
-
-
# Loads a marshalled Timezone.
-
1
def self._load(data)
-
Timezone.get(data)
-
end
-
-
1
private
-
# Loads in the index of timezones if it hasn't already been loaded.
-
1
def self.load_index
-
unless @@index_loaded
-
require 'tzinfo/indexes/timezones'
-
@@index_loaded = true
-
end
-
end
-
-
# Returns an array of proxies corresponding to the given array of
-
# identifiers.
-
1
def self.get_proxies(identifiers)
-
identifiers.collect {|identifier| get_proxy(identifier)}
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
-
# TimezoneDefinition is included into Timezone definition modules.
-
# TimezoneDefinition provides the methods for defining timezones.
-
1
module TimezoneDefinition #:nodoc:
-
# Add class methods to the includee.
-
1
def self.append_features(base)
-
3
super
-
3
base.extend(ClassMethods)
-
end
-
-
# Class methods for inclusion.
-
1
module ClassMethods #:nodoc:
-
# Returns and yields a DataTimezoneInfo object to define a timezone.
-
1
def timezone(identifier)
-
3
yield @timezone = DataTimezoneInfo.new(identifier)
-
end
-
-
# Defines a linked timezone.
-
1
def linked_timezone(identifier, link_to_identifier)
-
@timezone = LinkedTimezoneInfo.new(identifier, link_to_identifier)
-
end
-
-
# Returns the last TimezoneInfo to be defined with timezone or
-
# linked_timezone.
-
1
def get
-
3
@timezone
-
end
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# The timezone index file includes TimezoneIndexDefinition which provides
-
# methods used to define timezones in the index.
-
1
module TimezoneIndexDefinition #:nodoc:
-
1
def self.append_features(base)
-
super
-
base.extend(ClassMethods)
-
base.instance_eval do
-
@timezones = []
-
@data_timezones = []
-
@linked_timezones = []
-
end
-
end
-
-
1
module ClassMethods #:nodoc:
-
# Defines a timezone based on data.
-
1
def timezone(identifier)
-
@timezones << identifier
-
@data_timezones << identifier
-
end
-
-
# Defines a timezone which is a link to another timezone.
-
1
def linked_timezone(identifier)
-
@timezones << identifier
-
@linked_timezones << identifier
-
end
-
-
# Returns a frozen array containing the identifiers of all the timezones.
-
# Identifiers appear in the order they were defined in the index.
-
1
def timezones
-
@timezones.freeze
-
end
-
-
# Returns a frozen array containing the identifiers of all data timezones.
-
# Identifiers appear in the order they were defined in the index.
-
1
def data_timezones
-
@data_timezones.freeze
-
end
-
-
# Returns a frozen array containing the identifiers of all linked
-
# timezones. Identifiers appear in the order they were defined in
-
# the index.
-
1
def linked_timezones
-
@linked_timezones.freeze
-
end
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# Represents a timezone defined in a data module.
-
1
class TimezoneInfo #:nodoc:
-
-
# The timezone identifier.
-
1
attr_reader :identifier
-
-
# Constructs a new TimezoneInfo with an identifier.
-
1
def initialize(identifier)
-
3
@identifier = identifier
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
"#<#{self.class}: #@identifier>"
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# Represents an offset defined in a Timezone data file.
-
1
class TimezoneOffsetInfo #:nodoc:
-
# The base offset of the timezone from UTC in seconds.
-
1
attr_reader :utc_offset
-
-
# The offset from standard time for the zone in seconds (i.e. non-zero if
-
# daylight savings is being observed).
-
1
attr_reader :std_offset
-
-
# The total offset of this observance from UTC in seconds
-
# (utc_offset + std_offset).
-
1
attr_reader :utc_total_offset
-
-
# The abbreviation that identifies this observance, e.g. "GMT"
-
# (Greenwich Mean Time) or "BST" (British Summer Time) for "Europe/London". The returned identifier is a
-
# symbol.
-
1
attr_reader :abbreviation
-
-
# Constructs a new TimezoneOffsetInfo. utc_offset and std_offset are
-
# specified in seconds.
-
1
def initialize(utc_offset, std_offset, abbreviation)
-
15
@utc_offset = utc_offset
-
15
@std_offset = std_offset
-
15
@abbreviation = abbreviation
-
-
15
@utc_total_offset = @utc_offset + @std_offset
-
end
-
-
# True if std_offset is non-zero.
-
1
def dst?
-
@std_offset != 0
-
end
-
-
# Converts a UTC DateTime to local time based on the offset of this period.
-
1
def to_local(utc)
-
3
TimeOrDateTime.wrap(utc) {|wrapped|
-
3
wrapped + @utc_total_offset
-
}
-
end
-
-
# Converts a local DateTime to UTC based on the offset of this period.
-
1
def to_utc(local)
-
78
TimeOrDateTime.wrap(local) {|wrapped|
-
78
wrapped - @utc_total_offset
-
}
-
end
-
-
# Returns true if and only if toi has the same utc_offset, std_offset
-
# and abbreviation as this TimezoneOffsetInfo.
-
1
def ==(toi)
-
toi.respond_to?(:utc_offset) && toi.respond_to?(:std_offset) && toi.respond_to?(:abbreviation) &&
-
utc_offset == toi.utc_offset && std_offset == toi.std_offset && abbreviation == toi.abbreviation
-
end
-
-
# Returns true if and only if toi has the same utc_offset, std_offset
-
# and abbreviation as this TimezoneOffsetInfo.
-
1
def eql?(toi)
-
self == toi
-
end
-
-
# Returns a hash of this TimezoneOffsetInfo.
-
1
def hash
-
utc_offset.hash ^ std_offset.hash ^ abbreviation.hash
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
"#<#{self.class}: #@utc_offset,#@std_offset,#@abbreviation>"
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2005-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
# A period of time in a timezone where the same offset from UTC applies.
-
#
-
# All the methods that take times accept instances of Time, DateTime or
-
# integer timestamps.
-
1
class TimezonePeriod
-
# The TimezoneTransitionInfo that defines the start of this TimezonePeriod
-
# (may be nil if unbounded).
-
1
attr_reader :start_transition
-
-
# The TimezoneTransitionInfo that defines the end of this TimezonePeriod
-
# (may be nil if unbounded).
-
1
attr_reader :end_transition
-
-
# The TimezoneOffsetInfo for this period.
-
1
attr_reader :offset
-
-
# Initializes a new TimezonePeriod.
-
1
def initialize(start_transition, end_transition, offset = nil)
-
186
@start_transition = start_transition
-
186
@end_transition = end_transition
-
-
186
if offset
-
6
raise ArgumentError, 'Offset specified with transitions' if @start_transition || @end_transition
-
6
@offset = offset
-
else
-
180
if @start_transition
-
180
@offset = @start_transition.offset
-
elsif @end_transition
-
@offset = @end_transition.previous_offset
-
else
-
raise ArgumentError, 'No offset specified and no transitions to determine it from'
-
end
-
end
-
-
186
@utc_total_offset_rational = nil
-
end
-
-
# Base offset of the timezone from UTC (seconds).
-
1
def utc_offset
-
@offset.utc_offset
-
end
-
-
# Offset from the local time where daylight savings is in effect (seconds).
-
# E.g.: utc_offset could be -5 hours. Normally, std_offset would be 0.
-
# During daylight savings, std_offset would typically become +1 hours.
-
1
def std_offset
-
@offset.std_offset
-
end
-
-
# The identifier of this period, e.g. "GMT" (Greenwich Mean Time) or "BST"
-
# (British Summer Time) for "Europe/London". The returned identifier is a
-
# symbol.
-
1
def abbreviation
-
6
@offset.abbreviation
-
end
-
1
alias :zone_identifier :abbreviation
-
-
# Total offset from UTC (seconds). Equal to utc_offset + std_offset.
-
1
def utc_total_offset
-
18
@offset.utc_total_offset
-
end
-
-
# Total offset from UTC (days). Result is a Rational.
-
1
def utc_total_offset_rational
-
unless @utc_total_offset_rational
-
@utc_total_offset_rational = OffsetRationals.rational_for_offset(utc_total_offset)
-
end
-
@utc_total_offset_rational
-
end
-
-
# The start time of the period in UTC as a DateTime. May be nil if unbounded.
-
1
def utc_start
-
@start_transition ? @start_transition.at.to_datetime : nil
-
end
-
-
# The end time of the period in UTC as a DateTime. May be nil if unbounded.
-
1
def utc_end
-
@end_transition ? @end_transition.at.to_datetime : nil
-
end
-
-
# The start time of the period in local time as a DateTime. May be nil if
-
# unbounded.
-
1
def local_start
-
@start_transition ? @start_transition.local_start.to_datetime : nil
-
end
-
-
# The end time of the period in local time as a DateTime. May be nil if
-
# unbounded.
-
1
def local_end
-
@end_transition ? @end_transition.local_end.to_datetime : nil
-
end
-
-
# true if daylight savings is in effect for this period; otherwise false.
-
1
def dst?
-
@offset.dst?
-
end
-
-
# true if this period is valid for the given UTC DateTime; otherwise false.
-
1
def valid_for_utc?(utc)
-
utc_after_start?(utc) && utc_before_end?(utc)
-
end
-
-
# true if the given UTC DateTime is after the start of the period
-
# (inclusive); otherwise false.
-
1
def utc_after_start?(utc)
-
!@start_transition || @start_transition.at <= utc
-
end
-
-
# true if the given UTC DateTime is before the end of the period
-
# (exclusive); otherwise false.
-
1
def utc_before_end?(utc)
-
!@end_transition || @end_transition.at > utc
-
end
-
-
# true if this period is valid for the given local DateTime; otherwise false.
-
1
def valid_for_local?(local)
-
local_after_start?(local) && local_before_end?(local)
-
end
-
-
# true if the given local DateTime is after the start of the period
-
# (inclusive); otherwise false.
-
1
def local_after_start?(local)
-
!@start_transition || @start_transition.local_start <= local
-
end
-
-
# true if the given local DateTime is before the end of the period
-
# (exclusive); otherwise false.
-
1
def local_before_end?(local)
-
!@end_transition || @end_transition.local_end > local
-
end
-
-
# Converts a UTC DateTime to local time based on the offset of this period.
-
1
def to_local(utc)
-
3
@offset.to_local(utc)
-
end
-
-
# Converts a local DateTime to UTC based on the offset of this period.
-
1
def to_utc(local)
-
78
@offset.to_utc(local)
-
end
-
-
# Returns true if this TimezonePeriod is equal to p. This compares the
-
# start_transition, end_transition and offset using ==.
-
1
def ==(p)
-
p.respond_to?(:start_transition) && p.respond_to?(:end_transition) &&
-
p.respond_to?(:offset) && start_transition == p.start_transition &&
-
end_transition == p.end_transition && offset == p.offset
-
end
-
-
# Returns true if this TimezonePeriods is equal to p. This compares the
-
# start_transition, end_transition and offset using eql?
-
1
def eql?(p)
-
p.respond_to?(:start_transition) && p.respond_to?(:end_transition) &&
-
p.respond_to?(:offset) && start_transition.eql?(p.start_transition) &&
-
end_transition.eql?(p.end_transition) && offset.eql?(p.offset)
-
end
-
-
# Returns a hash of this TimezonePeriod.
-
1
def hash
-
result = @start_transition.hash ^ @end_transition.hash
-
result ^= @offset.hash unless @start_transition || @end_transition
-
result
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
result = "#<#{self.class}: #{@start_transition.inspect},#{@end_transition.inspect}"
-
result << ",#{@offset.inspect}>" unless @start_transition || @end_transition
-
result + '>'
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2005-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
module TZInfo
-
-
# A proxy class representing a timezone with a given identifier. TimezoneProxy
-
# inherits from Timezone and can be treated like any Timezone loaded with
-
# Timezone.get.
-
#
-
# The first time an attempt is made to access the data for the timezone, the
-
# real Timezone is loaded. If the proxy's identifier was not valid, then an
-
# exception will be raised at this point.
-
1
class TimezoneProxy < Timezone
-
# Construct a new TimezoneProxy for the given identifier. The identifier
-
# is not checked when constructing the proxy. It will be validated on the
-
# when the real Timezone is loaded.
-
1
def self.new(identifier)
-
# Need to override new to undo the behaviour introduced in Timezone#new.
-
4
tzp = super()
-
4
tzp.send(:setup, identifier)
-
4
tzp
-
end
-
-
# The identifier of the timezone, e.g. "Europe/Paris".
-
1
def identifier
-
@real_timezone ? @real_timezone.identifier : @identifier
-
end
-
-
# Returns the TimezonePeriod for the given UTC time. utc can either be
-
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
-
# information in utc is ignored (it is treated as a UTC time).
-
1
def period_for_utc(utc)
-
3
real_timezone.period_for_utc(utc)
-
end
-
-
# Returns the set of TimezonePeriod instances that are valid for the given
-
# local time as an array. If you just want a single period, use
-
# period_for_local instead and specify how abiguities should be resolved.
-
# Returns an empty array if no periods are found for the given time.
-
1
def periods_for_local(local)
-
183
real_timezone.periods_for_local(local)
-
end
-
-
# Dumps this TimezoneProxy for marshalling.
-
1
def _dump(limit)
-
identifier
-
end
-
-
# Loads a marshalled TimezoneProxy.
-
1
def self._load(data)
-
TimezoneProxy.new(data)
-
end
-
-
1
private
-
1
def setup(identifier)
-
4
@identifier = identifier
-
4
@real_timezone = nil
-
end
-
-
1
def real_timezone
-
186
@real_timezone ||= Timezone.get(@identifier)
-
end
-
end
-
end
-
#--
-
# Copyright (c) 2006-2010 Philip Ross
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
-
# of this software and associated documentation files (the "Software"), to deal
-
# in the Software without restriction, including without limitation the rights
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-
# copies of the Software, and to permit persons to whom the Software is
-
# furnished to do so, subject to the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be included in all
-
# copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-
# THE SOFTWARE.
-
#++
-
-
1
require 'date'
-
-
1
module TZInfo
-
# Represents an offset defined in a Timezone data file.
-
1
class TimezoneTransitionInfo #:nodoc:
-
# The offset this transition changes to (a TimezoneOffsetInfo instance).
-
1
attr_reader :offset
-
-
# The offset this transition changes from (a TimezoneOffsetInfo instance).
-
1
attr_reader :previous_offset
-
-
# The numerator of the DateTime if the transition time is defined as a
-
# DateTime, otherwise the transition time as a timestamp.
-
1
attr_reader :numerator_or_time
-
1
protected :numerator_or_time
-
-
# Either the denominotor of the DateTime if the transition time is defined
-
# as a DateTime, otherwise nil.
-
1
attr_reader :denominator
-
1
protected :denominator
-
-
# Creates a new TimezoneTransitionInfo with the given offset,
-
# previous_offset (both TimezoneOffsetInfo instances) and UTC time.
-
# if denominator is nil, numerator_or_time is treated as a number of
-
# seconds since the epoch. If denominator is specified numerator_or_time
-
# and denominator are used to create a DateTime as follows:
-
#
-
# DateTime.new!(Rational.send(:new!, numerator_or_time, denominator), 0, Date::ITALY)
-
#
-
# For performance reasons, the numerator and denominator must be specified
-
# in their lowest form.
-
1
def initialize(offset, previous_offset, numerator_or_time, denominator = nil)
-
176
@offset = offset
-
176
@previous_offset = previous_offset
-
176
@numerator_or_time = numerator_or_time
-
176
@denominator = denominator
-
-
176
@at = nil
-
176
@local_end = nil
-
176
@local_start = nil
-
end
-
-
# A TimeOrDateTime instance representing the UTC time when this transition
-
# occurs.
-
1
def at
-
41
unless @at
-
20
unless @denominator
-
19
@at = TimeOrDateTime.new(@numerator_or_time)
-
else
-
1
r = RubyCoreSupport.rational_new!(@numerator_or_time, @denominator)
-
1
dt = RubyCoreSupport.datetime_new!(r, 0, Date::ITALY)
-
1
@at = TimeOrDateTime.new(dt)
-
end
-
end
-
-
41
@at
-
end
-
-
# A TimeOrDateTime instance representing the local time when this transition
-
# causes the previous observance to end (calculated from at using
-
# previous_offset).
-
1
def local_end
-
139
@local_end = at.add_with_convert(@previous_offset.utc_total_offset) unless @local_end
-
139
@local_end
-
end
-
-
# A TimeOrDateTime instance representing the local time when this transition
-
# causes the next observance to start (calculated from at using offset).
-
1
def local_start
-
295
@local_start = at.add_with_convert(@offset.utc_total_offset) unless @local_start
-
295
@local_start
-
end
-
-
# Returns true if this TimezoneTransitionInfo is equal to the given
-
# TimezoneTransitionInfo. Two TimezoneTransitionInfo instances are
-
# considered to be equal by == if offset, previous_offset and at are all
-
# equal.
-
1
def ==(tti)
-
tti.respond_to?(:offset) && tti.respond_to?(:previous_offset) && tti.respond_to?(:at) &&
-
offset == tti.offset && previous_offset == tti.previous_offset && at == tti.at
-
end
-
-
# Returns true if this TimezoneTransitionInfo is equal to the given
-
# TimezoneTransitionInfo. Two TimezoneTransitionInfo instances are
-
# considered to be equal by eql? if offset, previous_offset,
-
# numerator_or_time and denominator are all equal. This is stronger than ==,
-
# which just requires the at times to be equal regardless of how they were
-
# originally specified.
-
1
def eql?(tti)
-
tti.respond_to?(:offset) && tti.respond_to?(:previous_offset) &&
-
tti.respond_to?(:numerator_or_time) && tti.respond_to?(:denominator) &&
-
offset == tti.offset && previous_offset == tti.previous_offset &&
-
numerator_or_time == tti.numerator_or_time && denominator == tti.denominator
-
end
-
-
# Returns a hash of this TimezoneTransitionInfo instance.
-
1
def hash
-
@offset.hash ^ @previous_offset.hash ^ @numerator_or_time.hash ^ @denominator.hash
-
end
-
-
# Returns internal object state as a programmer-readable string.
-
1
def inspect
-
"#<#{self.class}: #{at.inspect},#{@offset.inspect}>"
-
end
-
end
-
end